✨ Real feel-less refresh token
This commit is contained in:
		@@ -9,6 +9,7 @@
 | 
			
		||||
    "preview": "vite preview"
 | 
			
		||||
  },
 | 
			
		||||
  "dependencies": {
 | 
			
		||||
    "@fortawesome/fontawesome-free": "^6.5.1",
 | 
			
		||||
    "@solidjs/router": "^0.10.10",
 | 
			
		||||
    "solid-js": "^1.8.7",
 | 
			
		||||
    "universal-cookie": "^7.0.2"
 | 
			
		||||
 
 | 
			
		||||
@@ -8,6 +8,8 @@ import "./assets/fonts/fonts.css";
 | 
			
		||||
import { lazy } from "solid-js";
 | 
			
		||||
import { Route, Router } from "@solidjs/router";
 | 
			
		||||
 | 
			
		||||
import "@fortawesome/fontawesome-free/css/all.min.css";
 | 
			
		||||
 | 
			
		||||
import RootLayout from "./layouts/RootLayout.tsx";
 | 
			
		||||
import { UserinfoProvider } from "./stores/userinfo.tsx";
 | 
			
		||||
import { WellKnownProvider } from "./stores/wellKnown.tsx";
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
import Navbar from "./shared/Navbar.tsx";
 | 
			
		||||
import Navigatior from "./shared/Navigatior.tsx";
 | 
			
		||||
import { readProfiles, useUserinfo } from "../stores/userinfo.tsx";
 | 
			
		||||
import { createEffect, createMemo, createSignal, Show } from "solid-js";
 | 
			
		||||
import { readWellKnown } from "../stores/wellKnown.tsx";
 | 
			
		||||
@@ -52,7 +52,7 @@ export default function RootLayout(props: any) {
 | 
			
		||||
      </div>
 | 
			
		||||
    }>
 | 
			
		||||
      <Show when={!searchParams["embedded"]}>
 | 
			
		||||
        <Navbar />
 | 
			
		||||
        <Navigatior />
 | 
			
		||||
      </Show>
 | 
			
		||||
 | 
			
		||||
      <main class={`${mainContentStyles()} px-5`}>{props.children}</main>
 | 
			
		||||
 
 | 
			
		||||
@@ -8,7 +8,7 @@ interface MenuItem {
 | 
			
		||||
  children?: MenuItem[];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default function Navbar() {
 | 
			
		||||
export default function Navigatior() {
 | 
			
		||||
  const nav: MenuItem[] = [
 | 
			
		||||
    {
 | 
			
		||||
      label: "You", children: [
 | 
			
		||||
@@ -1,7 +1,6 @@
 | 
			
		||||
import { readProfiles } from "../../stores/userinfo.tsx";
 | 
			
		||||
import { useNavigate, useSearchParams } from "@solidjs/router";
 | 
			
		||||
import { createSignal, For, Match, Show, Switch } from "solid-js";
 | 
			
		||||
import Cookie from "universal-cookie";
 | 
			
		||||
 | 
			
		||||
export default function LoginPage() {
 | 
			
		||||
  const [title, setTitle] = createSignal("Sign in");
 | 
			
		||||
@@ -116,9 +115,6 @@ export default function LoginPage() {
 | 
			
		||||
      setError(err);
 | 
			
		||||
      throw new Error(err);
 | 
			
		||||
    } else {
 | 
			
		||||
      const data = await res.json();
 | 
			
		||||
      new Cookie().set("access_token", data["access_token"], { path: "/", maxAge: undefined });
 | 
			
		||||
      new Cookie().set("refresh_token", data["refresh_token"], { path: "/", maxAge: undefined });
 | 
			
		||||
      setError(null);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,5 @@
 | 
			
		||||
import { getAtk } from "../stores/userinfo.tsx";
 | 
			
		||||
import { createSignal, For, Show } from "solid-js";
 | 
			
		||||
import { createSignal, For, Match, Show, Switch } from "solid-js";
 | 
			
		||||
 | 
			
		||||
export default function DashboardPage() {
 | 
			
		||||
  const [challenges, setChallenges] = createSignal<any[]>([]);
 | 
			
		||||
@@ -12,6 +12,8 @@ export default function DashboardPage() {
 | 
			
		||||
  const [error, setError] = createSignal<string | null>(null);
 | 
			
		||||
  const [submitting, setSubmitting] = createSignal(false);
 | 
			
		||||
 | 
			
		||||
  const [contentTab, setContentTab] = createSignal(0);
 | 
			
		||||
 | 
			
		||||
  async function readChallenges() {
 | 
			
		||||
    const res = await fetch("/api/users/me/challenges?take=10", {
 | 
			
		||||
      headers: { Authorization: `Bearer ${getAtk()}` }
 | 
			
		||||
@@ -94,11 +96,7 @@ export default function DashboardPage() {
 | 
			
		||||
        <div class="stats shadow w-full">
 | 
			
		||||
          <div class="stat">
 | 
			
		||||
            <div class="stat-figure text-secondary">
 | 
			
		||||
              <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"
 | 
			
		||||
                   class="inline-block w-8 h-8 stroke-current">
 | 
			
		||||
                <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
 | 
			
		||||
                      d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path>
 | 
			
		||||
              </svg>
 | 
			
		||||
              <i class="fa-solid fa-door-open inline-block text-[28px] w-8 h-8 stroke-current"></i>
 | 
			
		||||
            </div>
 | 
			
		||||
            <div class="stat-title">Challenges</div>
 | 
			
		||||
            <div class="stat-value">{challengeCount()}</div>
 | 
			
		||||
@@ -106,11 +104,7 @@ export default function DashboardPage() {
 | 
			
		||||
 | 
			
		||||
          <div class="stat">
 | 
			
		||||
            <div class="stat-figure text-secondary">
 | 
			
		||||
              <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"
 | 
			
		||||
                   class="inline-block w-8 h-8 stroke-current">
 | 
			
		||||
                <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
 | 
			
		||||
                      d="M12 6V4m0 2a2 2 0 100 4m0-4a2 2 0 110 4m-6 8a2 2 0 100-4m0 4a2 2 0 110-4m0 4v2m0-6V4m6 6v10m6-2a2 2 0 100-4m0 4a2 2 0 110-4m0 4v2m0-6V4"></path>
 | 
			
		||||
              </svg>
 | 
			
		||||
              <i class="fa-solid fa-key inline-block text-[28px] w-8 h-8 stroke-current"></i>
 | 
			
		||||
            </div>
 | 
			
		||||
            <div class="stat-title">Sessions</div>
 | 
			
		||||
            <div class="stat-value">{sessionCount()}</div>
 | 
			
		||||
@@ -118,11 +112,7 @@ export default function DashboardPage() {
 | 
			
		||||
 | 
			
		||||
          <div class="stat">
 | 
			
		||||
            <div class="stat-figure text-secondary">
 | 
			
		||||
              <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"
 | 
			
		||||
                   class="inline-block w-8 h-8 stroke-current">
 | 
			
		||||
                <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
 | 
			
		||||
                      d="M5 8h14M5 8a2 2 0 110-4h14a2 2 0 110 4M5 8v10a2 2 0 002 2h10a2 2 0 002-2V8m-9 4h4"></path>
 | 
			
		||||
              </svg>
 | 
			
		||||
              <i class="fa-solid fa-person-walking inline-block text-[28px] w-8 h-8 stroke-current"></i>
 | 
			
		||||
            </div>
 | 
			
		||||
            <div class="stat-title">Events</div>
 | 
			
		||||
            <div class="stat-value">{eventCount()}</div>
 | 
			
		||||
@@ -130,50 +120,52 @@ export default function DashboardPage() {
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
 | 
			
		||||
      <div id="data-area" class="mt-5 shadow">
 | 
			
		||||
        <div class="join join-vertical w-full">
 | 
			
		||||
      <div id="switch-area" class="mt-5">
 | 
			
		||||
        <div role="tablist" class="tabs tabs-boxed">
 | 
			
		||||
          <input type="radio" name="content-switch" role="tab" class="tab" aria-label="Challenges"
 | 
			
		||||
                 checked={contentTab() === 0} onChange={() => setContentTab(0)} />
 | 
			
		||||
          <input type="radio" name="content-switch" role="tab" class="tab" aria-label="Sessions"
 | 
			
		||||
                 checked={contentTab() === 1} onChange={() => setContentTab(1)} />
 | 
			
		||||
          <input type="radio" name="content-switch" role="tab" class="tab" aria-label="Events"
 | 
			
		||||
                 checked={contentTab() === 2} onChange={() => setContentTab(2)} />
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
 | 
			
		||||
          <details class="collapse collapse-plus join-item border-b border-base-200">
 | 
			
		||||
            <summary class="collapse-title text-lg font-medium">
 | 
			
		||||
              Challenges
 | 
			
		||||
            </summary>
 | 
			
		||||
            <div class="collapse-content mx-[-16px]">
 | 
			
		||||
              <div class="overflow-x-auto">
 | 
			
		||||
                <table class="table">
 | 
			
		||||
                  <thead>
 | 
			
		||||
                  <tr>
 | 
			
		||||
                    <th></th>
 | 
			
		||||
                    <th>State</th>
 | 
			
		||||
                    <th>IP Address</th>
 | 
			
		||||
                    <th>User Agent</th>
 | 
			
		||||
                    <th>Date</th>
 | 
			
		||||
                  </tr>
 | 
			
		||||
                  </thead>
 | 
			
		||||
                  <tbody>
 | 
			
		||||
                  <For each={challenges()}>
 | 
			
		||||
                    {item => <tr>
 | 
			
		||||
                      <th>{item.id}</th>
 | 
			
		||||
                      <td>{item.state}</td>
 | 
			
		||||
                      <td>{item.ip_address}</td>
 | 
			
		||||
                      <td>
 | 
			
		||||
      <div id="data-area" class="mt-5 shadow">
 | 
			
		||||
        <Switch>
 | 
			
		||||
          <Match when={contentTab() === 0}>
 | 
			
		||||
            <div class="overflow-x-auto">
 | 
			
		||||
              <table class="table">
 | 
			
		||||
                <thead>
 | 
			
		||||
                <tr>
 | 
			
		||||
                  <th></th>
 | 
			
		||||
                  <th>State</th>
 | 
			
		||||
                  <th>IP Address</th>
 | 
			
		||||
                  <th>User Agent</th>
 | 
			
		||||
                  <th>Date</th>
 | 
			
		||||
                </tr>
 | 
			
		||||
                </thead>
 | 
			
		||||
                <tbody>
 | 
			
		||||
                <For each={challenges()}>
 | 
			
		||||
                  {item => <tr>
 | 
			
		||||
                    <th>{item.id}</th>
 | 
			
		||||
                    <td>{item.state}</td>
 | 
			
		||||
                    <td>{item.ip_address}</td>
 | 
			
		||||
                    <td>
 | 
			
		||||
                        <span class="tooltip" data-tip={item.user_agent}>
 | 
			
		||||
                          {item.user_agent.substring(0, 10) + "..."}
 | 
			
		||||
                        </span>
 | 
			
		||||
                      </td>
 | 
			
		||||
                      <td>{new Date(item.created_at).toLocaleString()}</td>
 | 
			
		||||
                    </tr>}
 | 
			
		||||
                  </For>
 | 
			
		||||
                  </tbody>
 | 
			
		||||
                </table>
 | 
			
		||||
              </div>
 | 
			
		||||
                    </td>
 | 
			
		||||
                    <td>{new Date(item.created_at).toLocaleString()}</td>
 | 
			
		||||
                  </tr>}
 | 
			
		||||
                </For>
 | 
			
		||||
                </tbody>
 | 
			
		||||
              </table>
 | 
			
		||||
            </div>
 | 
			
		||||
          </details>
 | 
			
		||||
          </Match>
 | 
			
		||||
 | 
			
		||||
          <details class="collapse collapse-plus join-item border-b border-base-200">
 | 
			
		||||
            <summary class="collapse-title text-lg font-medium">
 | 
			
		||||
              Sessions
 | 
			
		||||
            </summary>
 | 
			
		||||
            <div class="collapse-content mx-[-16px]">
 | 
			
		||||
          <Match when={contentTab() === 1}>
 | 
			
		||||
            <div class="overflow-x-auto">
 | 
			
		||||
              <table class="table">
 | 
			
		||||
                <thead>
 | 
			
		||||
                <tr>
 | 
			
		||||
@@ -192,13 +184,8 @@ export default function DashboardPage() {
 | 
			
		||||
                    <td>{item.audiences?.join(", ")}</td>
 | 
			
		||||
                    <td>{new Date(item.created_at).toLocaleString()}</td>
 | 
			
		||||
                    <td class="py-0">
 | 
			
		||||
                      <button class="btn btn-sm btn-square btn-error" disabled={submitting()}
 | 
			
		||||
                              onClick={() => killSession(item)}>
 | 
			
		||||
                        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" class="h-5 w-5">
 | 
			
		||||
                          <path
 | 
			
		||||
                            d="M256 48C141.31 48 48 141.31 48 256s93.31 208 208 208s208-93.31 208-208S370.69 48 256 48zm80 224H176a16 16 0 0 1 0-32h160a16 16 0 0 1 0 32z"
 | 
			
		||||
                            fill="currentColor"></path>
 | 
			
		||||
                        </svg>
 | 
			
		||||
                      <button disabled={submitting()} onClick={() => killSession(item)}>
 | 
			
		||||
                        <i class="fa-solid fa-right-from-bracket"></i>
 | 
			
		||||
                      </button>
 | 
			
		||||
                    </td>
 | 
			
		||||
                  </tr>}
 | 
			
		||||
@@ -206,47 +193,41 @@ export default function DashboardPage() {
 | 
			
		||||
                </tbody>
 | 
			
		||||
              </table>
 | 
			
		||||
            </div>
 | 
			
		||||
          </details>
 | 
			
		||||
          </Match>
 | 
			
		||||
 | 
			
		||||
          <details class="collapse collapse-plus join-item">
 | 
			
		||||
            <summary class="collapse-title text-lg font-medium">
 | 
			
		||||
              Events
 | 
			
		||||
            </summary>
 | 
			
		||||
            <div class="collapse-content mx-[-16px]">
 | 
			
		||||
              <div class="overflow-x-auto">
 | 
			
		||||
                <table class="table">
 | 
			
		||||
                  <thead>
 | 
			
		||||
                  <tr>
 | 
			
		||||
                    <th></th>
 | 
			
		||||
                    <th>Type</th>
 | 
			
		||||
                    <th>Target</th>
 | 
			
		||||
                    <th>IP Address</th>
 | 
			
		||||
                    <th>User Agent</th>
 | 
			
		||||
                    <th>Date</th>
 | 
			
		||||
                  </tr>
 | 
			
		||||
                  </thead>
 | 
			
		||||
                  <tbody>
 | 
			
		||||
                  <For each={events()}>
 | 
			
		||||
                    {item => <tr>
 | 
			
		||||
                      <th>{item.id}</th>
 | 
			
		||||
                      <td>{item.type}</td>
 | 
			
		||||
                      <td>{item.target}</td>
 | 
			
		||||
                      <td>{item.ip_address}</td>
 | 
			
		||||
                      <td>
 | 
			
		||||
          <Match when={contentTab() === 2}>
 | 
			
		||||
            <div class="overflow-x-auto">
 | 
			
		||||
              <table class="table">
 | 
			
		||||
                <thead>
 | 
			
		||||
                <tr>
 | 
			
		||||
                  <th></th>
 | 
			
		||||
                  <th>Type</th>
 | 
			
		||||
                  <th>Target</th>
 | 
			
		||||
                  <th>IP Address</th>
 | 
			
		||||
                  <th>User Agent</th>
 | 
			
		||||
                  <th>Date</th>
 | 
			
		||||
                </tr>
 | 
			
		||||
                </thead>
 | 
			
		||||
                <tbody>
 | 
			
		||||
                <For each={events()}>
 | 
			
		||||
                  {item => <tr>
 | 
			
		||||
                    <th>{item.id}</th>
 | 
			
		||||
                    <td>{item.type}</td>
 | 
			
		||||
                    <td>{item.target}</td>
 | 
			
		||||
                    <td>{item.ip_address}</td>
 | 
			
		||||
                    <td>
 | 
			
		||||
                        <span class="tooltip" data-tip={item.user_agent}>
 | 
			
		||||
                          {item.user_agent.substring(0, 10) + "..."}
 | 
			
		||||
                        </span>
 | 
			
		||||
                      </td>
 | 
			
		||||
                      <td>{new Date(item.created_at).toLocaleString()}</td>
 | 
			
		||||
                    </tr>}
 | 
			
		||||
                  </For>
 | 
			
		||||
                  </tbody>
 | 
			
		||||
                </table>
 | 
			
		||||
              </div>
 | 
			
		||||
                    </td>
 | 
			
		||||
                    <td>{new Date(item.created_at).toLocaleString()}</td>
 | 
			
		||||
                  </tr>}
 | 
			
		||||
                </For>
 | 
			
		||||
                </tbody>
 | 
			
		||||
              </table>
 | 
			
		||||
            </div>
 | 
			
		||||
          </details>
 | 
			
		||||
 | 
			
		||||
        </div>
 | 
			
		||||
          </Match>
 | 
			
		||||
        </Switch>
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
  );
 | 
			
		||||
 
 | 
			
		||||
@@ -21,49 +21,23 @@ const defaultUserinfo: Userinfo = {
 | 
			
		||||
const [userinfo, setUserinfo] = createStore<Userinfo>(structuredClone(defaultUserinfo));
 | 
			
		||||
 | 
			
		||||
export function getAtk(): string {
 | 
			
		||||
  return new Cookie().get("access_token");
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export async function refreshAtk() {
 | 
			
		||||
  const rtk = new Cookie().get("refresh_token");
 | 
			
		||||
 | 
			
		||||
  const res = await fetch("/api/auth/token", {
 | 
			
		||||
    method: "POST",
 | 
			
		||||
    headers: { "Content-Type": "application/json" },
 | 
			
		||||
    body: JSON.stringify({
 | 
			
		||||
      refresh_token: rtk,
 | 
			
		||||
      grant_type: "refresh_token"
 | 
			
		||||
    })
 | 
			
		||||
  });
 | 
			
		||||
  if (res.status !== 200) {
 | 
			
		||||
    console.error(await res.text())
 | 
			
		||||
  } else {
 | 
			
		||||
    const data = await res.json();
 | 
			
		||||
    new Cookie().set("access_token", data["access_token"], { path: "/", maxAge: undefined });
 | 
			
		||||
    new Cookie().set("refresh_token", data["refresh_token"], { path: "/", maxAge: undefined });
 | 
			
		||||
  }
 | 
			
		||||
  return new Cookie().get("identity_auth_key");
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function checkLoggedIn(): boolean {
 | 
			
		||||
  return new Cookie().get("access_token");
 | 
			
		||||
  return new Cookie().get("identity_auth_key");
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export async function readProfiles(recovering = true) {
 | 
			
		||||
export async function readProfiles() {
 | 
			
		||||
  if (!checkLoggedIn()) return;
 | 
			
		||||
 | 
			
		||||
  const res = await fetch("/api/users/me", {
 | 
			
		||||
    headers: { "Authorization": `Bearer ${getAtk()}` }
 | 
			
		||||
    credentials: "include"
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  if (res.status !== 200) {
 | 
			
		||||
    if (recovering) {
 | 
			
		||||
      // Auto retry after refresh access token
 | 
			
		||||
      await refreshAtk();
 | 
			
		||||
      return await readProfiles(false);
 | 
			
		||||
    } else {
 | 
			
		||||
      clearUserinfo();
 | 
			
		||||
      window.location.reload();
 | 
			
		||||
    }
 | 
			
		||||
    clearUserinfo();
 | 
			
		||||
    window.location.reload();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  const data = await res.json();
 | 
			
		||||
@@ -77,8 +51,14 @@ export async function readProfiles(recovering = true) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function clearUserinfo() {
 | 
			
		||||
  new Cookie().remove("access_token", { path: "/", maxAge: undefined });
 | 
			
		||||
  new Cookie().remove("refresh_token", { path: "/", maxAge: undefined });
 | 
			
		||||
  const cookies = document.cookie.split(";");
 | 
			
		||||
  for (let i = 0; i < cookies.length; i++) {
 | 
			
		||||
    const cookie = cookies[i];
 | 
			
		||||
    const eqPos = cookie.indexOf("=");
 | 
			
		||||
    const name = eqPos > -1 ? cookie.substring(0, eqPos) : cookie;
 | 
			
		||||
    document.cookie = name + "=;expires=Thu, 01 Jan 1970 00:00:00 GMT";
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  setUserinfo(defaultUserinfo);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -335,6 +335,11 @@
 | 
			
		||||
  resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.19.12.tgz#c57c8afbb4054a3ab8317591a0b7320360b444ae"
 | 
			
		||||
  integrity sha512-T1QyPSDCyMXaO3pzBkF96E8xMkiRYbUEZADd29SyPGabqxMViNoii+NcK7eWJAEoU6RZyEm5lVSIjTmcdoB9HA==
 | 
			
		||||
 | 
			
		||||
"@fortawesome/fontawesome-free@^6.5.1":
 | 
			
		||||
  version "6.5.1"
 | 
			
		||||
  resolved "https://registry.yarnpkg.com/@fortawesome/fontawesome-free/-/fontawesome-free-6.5.1.tgz#55cc8410abf1003b726324661ce5b0d1c10de258"
 | 
			
		||||
  integrity sha512-CNy5vSwN3fsUStPRLX7fUYojyuzoEMSXPl7zSLJ8TgtRfjv24LOnOWKT2zYwaHZCJGkdyRnTmstR0P+Ah503Gw==
 | 
			
		||||
 | 
			
		||||
"@isaacs/cliui@^8.0.2":
 | 
			
		||||
  version "8.0.2"
 | 
			
		||||
  resolved "https://registry.yarnpkg.com/@isaacs/cliui/-/cliui-8.0.2.tgz#b37667b7bc181c168782259bab42474fbf52b550"
 | 
			
		||||
@@ -1472,6 +1477,7 @@ source-map-js@^1.0.2:
 | 
			
		||||
  integrity sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==
 | 
			
		||||
 | 
			
		||||
"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.1.0:
 | 
			
		||||
  name string-width-cjs
 | 
			
		||||
  version "4.2.3"
 | 
			
		||||
  resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
 | 
			
		||||
  integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
 | 
			
		||||
@@ -1490,6 +1496,7 @@ string-width@^5.0.1, string-width@^5.1.2:
 | 
			
		||||
    strip-ansi "^7.0.1"
 | 
			
		||||
 | 
			
		||||
"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1:
 | 
			
		||||
  name strip-ansi-cjs
 | 
			
		||||
  version "6.0.1"
 | 
			
		||||
  resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
 | 
			
		||||
  integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user