Adjust frontend auth behavior (#24)
This commit is contained in:
parent
5b8440f295
commit
04fbe6fc4d
@ -31,7 +31,9 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@esm-bundle/chai": "^4.3.4-fix.0",
|
"@esm-bundle/chai": "^4.3.4-fix.0",
|
||||||
"@lit/localize-tools": "^0.5.0",
|
"@lit/localize-tools": "^0.5.0",
|
||||||
|
"@open-wc/testing": "^3.0.3",
|
||||||
"@types/color": "^3.0.2",
|
"@types/color": "^3.0.2",
|
||||||
|
"@types/sinon": "^10.0.6",
|
||||||
"@typescript-eslint/eslint-plugin": "^5.4.0",
|
"@typescript-eslint/eslint-plugin": "^5.4.0",
|
||||||
"@typescript-eslint/parser": "^5.4.0",
|
"@typescript-eslint/parser": "^5.4.0",
|
||||||
"@web/dev-server-esbuild": "^0.2.16",
|
"@web/dev-server-esbuild": "^0.2.16",
|
||||||
|
|||||||
@ -1,9 +1,27 @@
|
|||||||
import { expect } from "@esm-bundle/chai";
|
import { spy, stub } from "sinon";
|
||||||
|
import { fixture, expect } from "@open-wc/testing";
|
||||||
|
// import { expect } from "@esm-bundle/chai";
|
||||||
|
|
||||||
import { App } from "./index";
|
import { App } from "./index";
|
||||||
|
|
||||||
describe("App", () => {
|
describe("browsertrix-app", () => {
|
||||||
it("should exist", () => {
|
it("is defined", async () => {
|
||||||
expect(App).to.exist;
|
const el = await fixture("<browsertrix-app></browsertrix-app>");
|
||||||
|
expect(el).instanceOf(App);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("gets auth state from local storage", async () => {
|
||||||
|
stub(window.localStorage, "getItem").callsFake((key) => {
|
||||||
|
if (key === "authState")
|
||||||
|
return JSON.stringify({
|
||||||
|
username: "test@example.com",
|
||||||
|
});
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
const el = (await fixture("<browsertrix-app></browsertrix-app>")) as App;
|
||||||
|
|
||||||
|
expect(el.authState).to.eql({
|
||||||
|
username: "test@example.com",
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import type { TemplateResult } from "lit";
|
import type { TemplateResult } from "lit";
|
||||||
|
import { state } from "lit/decorators.js";
|
||||||
import { msg, updateWhenLocaleChanges } from "@lit/localize";
|
import { msg, updateWhenLocaleChanges } from "@lit/localize";
|
||||||
|
|
||||||
import "./shoelace";
|
import "./shoelace";
|
||||||
@ -13,10 +14,22 @@ import type { ViewState, NavigateEvent } from "./utils/APIRouter";
|
|||||||
import type { AuthState } from "./types/auth";
|
import type { AuthState } from "./types/auth";
|
||||||
import theme from "./theme";
|
import theme from "./theme";
|
||||||
|
|
||||||
|
const ROUTES = {
|
||||||
|
home: "/",
|
||||||
|
login: "/log-in",
|
||||||
|
myAccount: "/my-account",
|
||||||
|
"archive-info": "/archive/:aid",
|
||||||
|
"archive-info-tab": "/archive/:aid/:tab",
|
||||||
|
} as const;
|
||||||
|
|
||||||
// ===========================================================================
|
// ===========================================================================
|
||||||
export class App extends LiteElement {
|
export class App extends LiteElement {
|
||||||
authState: AuthState | null;
|
|
||||||
router: APIRouter;
|
router: APIRouter;
|
||||||
|
|
||||||
|
@state()
|
||||||
|
authState: AuthState | null = null;
|
||||||
|
|
||||||
|
@state()
|
||||||
viewState: ViewState & {
|
viewState: ViewState & {
|
||||||
aid?: string;
|
aid?: string;
|
||||||
// TODO common tab type
|
// TODO common tab type
|
||||||
@ -31,31 +44,16 @@ export class App extends LiteElement {
|
|||||||
// history navigation.
|
// history navigation.
|
||||||
updateWhenLocaleChanges(this);
|
updateWhenLocaleChanges(this);
|
||||||
|
|
||||||
this.authState = null;
|
|
||||||
|
|
||||||
const authState = window.localStorage.getItem("authState");
|
const authState = window.localStorage.getItem("authState");
|
||||||
if (authState) {
|
if (authState) {
|
||||||
this.authState = JSON.parse(authState);
|
this.authState = JSON.parse(authState);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.router = new APIRouter({
|
this.router = new APIRouter(ROUTES);
|
||||||
home: "/",
|
|
||||||
login: "/log-in",
|
|
||||||
"my-account": "/my-account",
|
|
||||||
"archive-info": "/archive/:aid",
|
|
||||||
"archive-info-tab": "/archive/:aid/:tab",
|
|
||||||
});
|
|
||||||
|
|
||||||
this.viewState = this.router.match(window.location.pathname);
|
this.viewState = this.router.match(window.location.pathname);
|
||||||
}
|
}
|
||||||
|
|
||||||
static get properties() {
|
|
||||||
return {
|
|
||||||
viewState: { type: Object },
|
|
||||||
authState: { type: Object },
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
firstUpdated() {
|
firstUpdated() {
|
||||||
window.addEventListener("popstate", (event) => {
|
window.addEventListener("popstate", (event) => {
|
||||||
// if (event.state.view) {
|
// if (event.state.view) {
|
||||||
@ -71,10 +69,14 @@ export class App extends LiteElement {
|
|||||||
if (newView.startsWith("http")) {
|
if (newView.startsWith("http")) {
|
||||||
newView = new URL(newView).pathname;
|
newView = new URL(newView).pathname;
|
||||||
}
|
}
|
||||||
this.viewState = this.router.match(newView);
|
|
||||||
if (this.viewState._route === "login") {
|
if (newView === "/log-in" && this.authState) {
|
||||||
this.clearAuthState();
|
// Redirect to logged in home page
|
||||||
|
this.viewState = this.router.match(ROUTES.myAccount);
|
||||||
|
} else {
|
||||||
|
this.viewState = this.router.match(newView);
|
||||||
}
|
}
|
||||||
|
|
||||||
//console.log(this.view._route, window.location.href);
|
//console.log(this.view._route, window.location.href);
|
||||||
window.history.pushState(this.viewState, "", this.viewState._path);
|
window.history.pushState(this.viewState, "", this.viewState._path);
|
||||||
}
|
}
|
||||||
@ -149,7 +151,7 @@ export class App extends LiteElement {
|
|||||||
<div class="w-full flex flex-col md:flex-row">
|
<div class="w-full flex flex-col md:flex-row">
|
||||||
<nav class="md:w-80 md:p-4 md:border-r">
|
<nav class="md:w-80 md:p-4 md:border-r">
|
||||||
<ul class="flex md:flex-col">
|
<ul class="flex md:flex-col">
|
||||||
${navLink({ href: "/my-account", label: "Archives" })}
|
${navLink({ href: ROUTES.myAccount, label: "Archives" })}
|
||||||
${navLink({ href: "/users", label: "Users" })}
|
${navLink({ href: "/users", label: "Users" })}
|
||||||
</ul>
|
</ul>
|
||||||
</nav>
|
</nav>
|
||||||
@ -161,17 +163,23 @@ export class App extends LiteElement {
|
|||||||
case "login":
|
case "login":
|
||||||
return html`<log-in
|
return html`<log-in
|
||||||
class="w-full md:bg-gray-100 flex items-center justify-center"
|
class="w-full md:bg-gray-100 flex items-center justify-center"
|
||||||
|
@navigate="${this.onNavigateTo}"
|
||||||
@logged-in="${this.onLoggedIn}"
|
@logged-in="${this.onLoggedIn}"
|
||||||
|
.authState="${this.authState}"
|
||||||
></log-in>`;
|
></log-in>`;
|
||||||
|
|
||||||
case "home":
|
case "home":
|
||||||
return html`<div class="w-full flex items-center justify-center">
|
return html`<div class="w-full flex items-center justify-center">
|
||||||
<sl-button type="primary" size="large" @click="${this.onNeedLogin}">
|
<sl-button
|
||||||
|
type="primary"
|
||||||
|
size="large"
|
||||||
|
@click="${() => this.navigate("/log-in")}"
|
||||||
|
>
|
||||||
${msg("Log In")}
|
${msg("Log In")}
|
||||||
</sl-button>
|
</sl-button>
|
||||||
</div>`;
|
</div>`;
|
||||||
|
|
||||||
case "my-account":
|
case "myAccount":
|
||||||
return appLayout(html`<my-account
|
return appLayout(html`<my-account
|
||||||
class="w-full"
|
class="w-full"
|
||||||
@navigate="${this.onNavigateTo}"
|
@navigate="${this.onNavigateTo}"
|
||||||
@ -206,12 +214,16 @@ export class App extends LiteElement {
|
|||||||
headers: { Authorization: event.detail.auth },
|
headers: { Authorization: event.detail.auth },
|
||||||
};
|
};
|
||||||
window.localStorage.setItem("authState", JSON.stringify(this.authState));
|
window.localStorage.setItem("authState", JSON.stringify(this.authState));
|
||||||
this.navigate("/my-account");
|
this.navigate(ROUTES.myAccount);
|
||||||
}
|
}
|
||||||
|
|
||||||
onNeedLogin() {
|
onNeedLogin(event?: CustomEvent<{ api: boolean }>) {
|
||||||
this.clearAuthState();
|
this.clearAuthState();
|
||||||
this.navigate("/log-in");
|
|
||||||
|
if (event?.detail?.api) {
|
||||||
|
// TODO refresh instead of redirect
|
||||||
|
}
|
||||||
|
this.navigate(ROUTES.login);
|
||||||
}
|
}
|
||||||
|
|
||||||
onNavigateTo(event: NavigateEvent) {
|
onNavigateTo(event: NavigateEvent) {
|
||||||
|
|||||||
@ -1,11 +1,12 @@
|
|||||||
import LiteElement, { html } from "../utils/LiteElement";
|
import LiteElement, { html } from "../utils/LiteElement";
|
||||||
|
import { needLogin } from "../utils/auth";
|
||||||
import type { Archive, ArchiveConfig } from "../types/archives";
|
import type { Archive, ArchiveConfig } from "../types/archives";
|
||||||
import type { AuthState } from "../types/auth";
|
import type { AuthState } from "../types/auth";
|
||||||
|
|
||||||
|
@needLogin
|
||||||
export class ArchiveConfigsPage extends LiteElement {
|
export class ArchiveConfigsPage extends LiteElement {
|
||||||
archive!: Archive & {
|
archive!: Archive;
|
||||||
authState: AuthState;
|
authState!: AuthState;
|
||||||
};
|
|
||||||
configs: ArchiveConfig;
|
configs: ArchiveConfig;
|
||||||
|
|
||||||
static get properties() {
|
static get properties() {
|
||||||
@ -16,14 +17,9 @@ export class ArchiveConfigsPage extends LiteElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async firstUpdated() {
|
async firstUpdated() {
|
||||||
if (!this.archive?.authState) {
|
|
||||||
// TODO
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const res = await this.apiFetch(
|
const res = await this.apiFetch(
|
||||||
`/archives/${this.archive.aid}/crawlconfigs`,
|
`/archives/${this.archive.aid}/crawlconfigs`,
|
||||||
this.archive.authState
|
this.authState!
|
||||||
);
|
);
|
||||||
this.configs = res.crawl_configs;
|
this.configs = res.crawl_configs;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,7 +1,9 @@
|
|||||||
import LiteElement, { html } from "../utils/LiteElement";
|
import LiteElement, { html } from "../utils/LiteElement";
|
||||||
|
import { needLogin } from "../utils/auth";
|
||||||
import type { Archive } from "../types/archives";
|
import type { Archive } from "../types/archives";
|
||||||
import type { AuthState } from "../types/auth";
|
import type { AuthState } from "../types/auth";
|
||||||
|
|
||||||
|
@needLogin
|
||||||
export class ArchivePage extends LiteElement {
|
export class ArchivePage extends LiteElement {
|
||||||
authState: AuthState = null;
|
authState: AuthState = null;
|
||||||
aid?: Archive["aid"];
|
aid?: Archive["aid"];
|
||||||
@ -48,8 +50,8 @@ export class ArchivePage extends LiteElement {
|
|||||||
? html`<btrix-archive-configs
|
? html`<btrix-archive-configs
|
||||||
.archive=${{
|
.archive=${{
|
||||||
aid: this.aid!,
|
aid: this.aid!,
|
||||||
authState: this.authState,
|
|
||||||
}}
|
}}
|
||||||
|
.authState=${this.authState}
|
||||||
></btrix-archive-configs>`
|
></btrix-archive-configs>`
|
||||||
: ""}
|
: ""}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -3,9 +3,6 @@ import LiteElement, { html } from "../utils/LiteElement";
|
|||||||
import type { Auth } from "../types/auth";
|
import type { Auth } from "../types/auth";
|
||||||
|
|
||||||
export class LogInPage extends LiteElement {
|
export class LogInPage extends LiteElement {
|
||||||
@property({ type: Object })
|
|
||||||
auth?: Auth;
|
|
||||||
|
|
||||||
@state()
|
@state()
|
||||||
isLoggingIn: boolean = false;
|
isLoggingIn: boolean = false;
|
||||||
|
|
||||||
@ -90,10 +87,6 @@ export class LogInPage extends LiteElement {
|
|||||||
console.error(e);
|
console.error(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this.auth) {
|
|
||||||
this.loginError = "Unknown login response";
|
|
||||||
}
|
|
||||||
|
|
||||||
this.isLoggingIn = false;
|
this.isLoggingIn = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,7 +1,9 @@
|
|||||||
import LiteElement, { html } from "../utils/LiteElement";
|
import LiteElement, { html } from "../utils/LiteElement";
|
||||||
|
import { needLogin } from "../utils/auth";
|
||||||
import type { Archive } from "../types/archives";
|
import type { Archive } from "../types/archives";
|
||||||
import type { AuthState } from "../types/auth";
|
import type { AuthState } from "../types/auth";
|
||||||
|
|
||||||
|
@needLogin
|
||||||
export class MyAccountPage extends LiteElement {
|
export class MyAccountPage extends LiteElement {
|
||||||
archiveList: Archive[] = [];
|
archiveList: Archive[] = [];
|
||||||
authState: AuthState = null;
|
authState: AuthState = null;
|
||||||
@ -15,15 +17,10 @@ export class MyAccountPage extends LiteElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async firstUpdated() {
|
async firstUpdated() {
|
||||||
if (!this.authState) {
|
const data = await this.apiFetch("/archives", this.authState!);
|
||||||
this.dispatchEvent(new CustomEvent("need-login"));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const data = await this.apiFetch("/archives", this.authState);
|
|
||||||
this.archiveList = data.archives;
|
this.archiveList = data.archives;
|
||||||
|
|
||||||
const data2 = await this.apiFetch("/users/me", this.authState);
|
const data2 = await this.apiFetch("/users/me", this.authState!);
|
||||||
this.id = data2.id;
|
this.id = data2.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -27,10 +27,27 @@ export default class LiteElement extends LitElement {
|
|||||||
|
|
||||||
async apiFetch(path: string, auth: Auth) {
|
async apiFetch(path: string, auth: Auth) {
|
||||||
const resp = await fetch("/api" + path, { headers: auth.headers });
|
const resp = await fetch("/api" + path, { headers: auth.headers });
|
||||||
|
|
||||||
if (resp.status !== 200) {
|
if (resp.status !== 200) {
|
||||||
this.navTo("/log-in");
|
if (resp.status === 401) {
|
||||||
throw new Error("logged out");
|
this.dispatchEvent(
|
||||||
|
new CustomEvent("need-login", {
|
||||||
|
detail: { api: true },
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO get error details
|
||||||
|
let errorMessage: string;
|
||||||
|
|
||||||
|
try {
|
||||||
|
errorMessage = (await resp.json()).detail;
|
||||||
|
} catch {
|
||||||
|
errorMessage = "Unknown API error";
|
||||||
|
}
|
||||||
|
throw new Error(errorMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
return await resp.json();
|
return await resp.json();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
26
frontend/src/utils/auth.test.ts
Normal file
26
frontend/src/utils/auth.test.ts
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
import { spy } from "sinon";
|
||||||
|
import { expect } from "@esm-bundle/chai";
|
||||||
|
|
||||||
|
import * as auth from "./auth";
|
||||||
|
|
||||||
|
describe("auth", () => {
|
||||||
|
describe("needLogin", () => {
|
||||||
|
it("dispatches the correct event on need log in", () => {
|
||||||
|
const dispatchEventSpy = spy();
|
||||||
|
class LiteElementMock {
|
||||||
|
dispatchEvent = dispatchEventSpy;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Element = auth.needLogin(
|
||||||
|
class extends LiteElementMock {
|
||||||
|
authState = null;
|
||||||
|
} as any
|
||||||
|
);
|
||||||
|
|
||||||
|
const element = new Element();
|
||||||
|
element.connectedCallback();
|
||||||
|
|
||||||
|
expect(dispatchEventSpy.getCall(0).firstArg.type).to.equal("need-login");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
33
frontend/src/utils/auth.ts
Normal file
33
frontend/src/utils/auth.ts
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
import LiteElement from "../utils/LiteElement";
|
||||||
|
import type { AuthState } from "../types/auth";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Block rendering and dispatch event if user is not logged in
|
||||||
|
*
|
||||||
|
* Usage example:
|
||||||
|
* ```ts
|
||||||
|
* @needLogin
|
||||||
|
* MyComponent extends LiteElement {}
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
export function needLogin<T extends { new (...args: any[]): LiteElement }>(
|
||||||
|
constructor: T
|
||||||
|
) {
|
||||||
|
return class extends constructor {
|
||||||
|
authState?: AuthState;
|
||||||
|
|
||||||
|
static get properties() {
|
||||||
|
return {
|
||||||
|
authState: { type: Object },
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
connectedCallback() {
|
||||||
|
if (this.authState) {
|
||||||
|
super.connectedCallback();
|
||||||
|
} else {
|
||||||
|
this.dispatchEvent(new CustomEvent("need-login"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
@ -59,6 +59,13 @@
|
|||||||
minimatch "^3.0.4"
|
minimatch "^3.0.4"
|
||||||
strip-json-comments "^3.1.1"
|
strip-json-comments "^3.1.1"
|
||||||
|
|
||||||
|
"@esm-bundle/chai@^4.3.4":
|
||||||
|
version "4.3.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/@esm-bundle/chai/-/chai-4.3.4.tgz#74ed4a0794b3a9f9517ff235744ac6f4be0d34dc"
|
||||||
|
integrity sha512-6Tx35wWiNw7X0nLY9RMx8v3EL8SacCFW+eEZOE9Hc+XxmU5HFE2AFEg+GehUZpiyDGwVvPH75ckGlqC7coIPnA==
|
||||||
|
dependencies:
|
||||||
|
"@types/chai" "^4.2.12"
|
||||||
|
|
||||||
"@esm-bundle/chai@^4.3.4-fix.0":
|
"@esm-bundle/chai@^4.3.4-fix.0":
|
||||||
version "4.3.4-fix.0"
|
version "4.3.4-fix.0"
|
||||||
resolved "https://registry.yarnpkg.com/@esm-bundle/chai/-/chai-4.3.4-fix.0.tgz#3084cff7eb46d741749f47f3a48dbbdcbaf30a92"
|
resolved "https://registry.yarnpkg.com/@esm-bundle/chai/-/chai-4.3.4-fix.0.tgz#3084cff7eb46d741749f47f3a48dbbdcbaf30a92"
|
||||||
@ -176,6 +183,63 @@
|
|||||||
"@nodelib/fs.scandir" "2.1.5"
|
"@nodelib/fs.scandir" "2.1.5"
|
||||||
fastq "^1.6.0"
|
fastq "^1.6.0"
|
||||||
|
|
||||||
|
"@open-wc/chai-dom-equals@^0.12.36":
|
||||||
|
version "0.12.36"
|
||||||
|
resolved "https://registry.yarnpkg.com/@open-wc/chai-dom-equals/-/chai-dom-equals-0.12.36.tgz#ed0eb56b9e98c4d7f7280facce6215654aae9f4c"
|
||||||
|
integrity sha512-Gt1fa37h4rtWPQGETSU4n1L678NmMi9KwHM1sH+JCGcz45rs8DBPx7MUVeGZ+HxRlbEI5t9LU2RGGv6xT2OlyA==
|
||||||
|
dependencies:
|
||||||
|
"@open-wc/semantic-dom-diff" "^0.13.16"
|
||||||
|
"@types/chai" "^4.1.7"
|
||||||
|
|
||||||
|
"@open-wc/dedupe-mixin@^1.3.0":
|
||||||
|
version "1.3.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@open-wc/dedupe-mixin/-/dedupe-mixin-1.3.0.tgz#0df5d438285fc3482838786ee81895318f0ff778"
|
||||||
|
integrity sha512-UfdK1MPnR6T7f3svzzYBfu3qBkkZ/KsPhcpc3JYhsUY4hbpwNF9wEQtD4Z+/mRqMTJrKg++YSxIxE0FBhY3RIw==
|
||||||
|
|
||||||
|
"@open-wc/scoped-elements@^2.0.1":
|
||||||
|
version "2.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/@open-wc/scoped-elements/-/scoped-elements-2.0.1.tgz#6b1c3535f809bd90710574db80093a81e3a1fc2d"
|
||||||
|
integrity sha512-JS6ozxUFwFX3+Er91v9yQzNIaFn7OnE0iESKTbFvkkKdNwvAPtp1fpckBKIvWk8Ae9ZcoI9DYZuT2DDbMPcadA==
|
||||||
|
dependencies:
|
||||||
|
"@lit/reactive-element" "^1.0.0"
|
||||||
|
"@open-wc/dedupe-mixin" "^1.3.0"
|
||||||
|
"@webcomponents/scoped-custom-element-registry" "^0.0.3"
|
||||||
|
|
||||||
|
"@open-wc/semantic-dom-diff@^0.13.16":
|
||||||
|
version "0.13.21"
|
||||||
|
resolved "https://registry.yarnpkg.com/@open-wc/semantic-dom-diff/-/semantic-dom-diff-0.13.21.tgz#718b9ec5f9a98935fc775e577ad094ae8d8b7dea"
|
||||||
|
integrity sha512-BONpjHcGX2zFa9mfnwBCLEmlDsOHzT+j6Qt1yfK3MzFXFtAykfzFjAgaxPetu0YbBlCfXuMlfxI4vlRGCGMvFg==
|
||||||
|
|
||||||
|
"@open-wc/semantic-dom-diff@^0.19.5":
|
||||||
|
version "0.19.5"
|
||||||
|
resolved "https://registry.yarnpkg.com/@open-wc/semantic-dom-diff/-/semantic-dom-diff-0.19.5.tgz#8d3d7f69140b9ba477a4adf8099c79e0efe18955"
|
||||||
|
integrity sha512-Wi0Fuj3dzqlWClU0y+J4k/nqTcH0uwgOWxZXPyeyG3DdvuyyjgiT4L4I/s6iVShWQvvEsyXnj7yVvixAo3CZvg==
|
||||||
|
dependencies:
|
||||||
|
"@types/chai" "^4.2.11"
|
||||||
|
"@web/test-runner-commands" "^0.5.7"
|
||||||
|
|
||||||
|
"@open-wc/testing-helpers@^2.0.2":
|
||||||
|
version "2.0.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/@open-wc/testing-helpers/-/testing-helpers-2.0.2.tgz#ca1833bf76036d9bdc03547415e79b6d502c78f6"
|
||||||
|
integrity sha512-wJlvDmWo+fIbgykRP21YSP9I9Pf/fo2+dZGaWG77Hw0sIuyB+7sNUDJDkL6kMkyyRecPV6dVRmbLt6HuOwvZ1w==
|
||||||
|
dependencies:
|
||||||
|
"@open-wc/scoped-elements" "^2.0.1"
|
||||||
|
lit "^2.0.0"
|
||||||
|
|
||||||
|
"@open-wc/testing@^3.0.3":
|
||||||
|
version "3.0.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/@open-wc/testing/-/testing-3.0.3.tgz#9d122933ef69e06cc9cb3b0d4e425b90117bb9e5"
|
||||||
|
integrity sha512-xJYckO8X9yfWc+ltPlDZjHGTh4ldNmnYsnxNriuUUEEhV5ASdsc+5WEsIS2+9m4lQELj89rNQ7YvhYhawDorhg==
|
||||||
|
dependencies:
|
||||||
|
"@esm-bundle/chai" "^4.3.4"
|
||||||
|
"@open-wc/chai-dom-equals" "^0.12.36"
|
||||||
|
"@open-wc/semantic-dom-diff" "^0.19.5"
|
||||||
|
"@open-wc/testing-helpers" "^2.0.2"
|
||||||
|
"@types/chai" "^4.2.11"
|
||||||
|
"@types/chai-dom" "^0.0.9"
|
||||||
|
"@types/sinon-chai" "^3.2.3"
|
||||||
|
chai-a11y-axe "^1.3.2"
|
||||||
|
|
||||||
"@popperjs/core@^2.7.0":
|
"@popperjs/core@^2.7.0":
|
||||||
version "2.10.2"
|
version "2.10.2"
|
||||||
resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.10.2.tgz#0798c03351f0dea1a5a4cabddf26a55a7cbee590"
|
resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.10.2.tgz#0798c03351f0dea1a5a4cabddf26a55a7cbee590"
|
||||||
@ -226,7 +290,7 @@
|
|||||||
dependencies:
|
dependencies:
|
||||||
type-detect "4.0.8"
|
type-detect "4.0.8"
|
||||||
|
|
||||||
"@sinonjs/fake-timers@^7.0.4":
|
"@sinonjs/fake-timers@^7.0.4", "@sinonjs/fake-timers@^7.1.0":
|
||||||
version "7.1.2"
|
version "7.1.2"
|
||||||
resolved "https://registry.yarnpkg.com/@sinonjs/fake-timers/-/fake-timers-7.1.2.tgz#2524eae70c4910edccf99b2f4e6efc5894aff7b5"
|
resolved "https://registry.yarnpkg.com/@sinonjs/fake-timers/-/fake-timers-7.1.2.tgz#2524eae70c4910edccf99b2f4e6efc5894aff7b5"
|
||||||
integrity sha512-iQADsW4LBMISqZ6Ci1dupJL9pprqwcVFTcOsEmQOEhW+KLCVn/Y4Jrvg2k19fIHCp+iFprriYPTdRcQR8NbUPg==
|
integrity sha512-iQADsW4LBMISqZ6Ci1dupJL9pprqwcVFTcOsEmQOEhW+KLCVn/Y4Jrvg2k19fIHCp+iFprriYPTdRcQR8NbUPg==
|
||||||
@ -274,7 +338,14 @@
|
|||||||
"@types/connect" "*"
|
"@types/connect" "*"
|
||||||
"@types/node" "*"
|
"@types/node" "*"
|
||||||
|
|
||||||
"@types/chai@^4.2.12":
|
"@types/chai-dom@^0.0.9":
|
||||||
|
version "0.0.9"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/chai-dom/-/chai-dom-0.0.9.tgz#77379354efec2568284ca355fff6a4f85f5a66f4"
|
||||||
|
integrity sha512-jj4F2NJog2/GBYsyJ8+NvhnWUBbPY4MUAKLdPJE6+568rw12GGXvj0ycUuP5nndVrnJgozmJAoMTvxvjJATXWw==
|
||||||
|
dependencies:
|
||||||
|
"@types/chai" "*"
|
||||||
|
|
||||||
|
"@types/chai@*", "@types/chai@^4.1.7", "@types/chai@^4.2.11", "@types/chai@^4.2.12":
|
||||||
version "4.2.22"
|
version "4.2.22"
|
||||||
resolved "https://registry.yarnpkg.com/@types/chai/-/chai-4.2.22.tgz#47020d7e4cf19194d43b5202f35f75bd2ad35ce7"
|
resolved "https://registry.yarnpkg.com/@types/chai/-/chai-4.2.22.tgz#47020d7e4cf19194d43b5202f35f75bd2ad35ce7"
|
||||||
integrity sha512-tFfcE+DSTzWAgifkjik9AySNqIyNoYwmR+uecPwwD/XRNfvOjmC/FjCxpiUGDkDVDphPfCUecSQVFw+lN3M3kQ==
|
integrity sha512-tFfcE+DSTzWAgifkjik9AySNqIyNoYwmR+uecPwwD/XRNfvOjmC/FjCxpiUGDkDVDphPfCUecSQVFw+lN3M3kQ==
|
||||||
@ -518,6 +589,21 @@
|
|||||||
"@types/mime" "^1"
|
"@types/mime" "^1"
|
||||||
"@types/node" "*"
|
"@types/node" "*"
|
||||||
|
|
||||||
|
"@types/sinon-chai@^3.2.3":
|
||||||
|
version "3.2.5"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/sinon-chai/-/sinon-chai-3.2.5.tgz#df21ae57b10757da0b26f512145c065f2ad45c48"
|
||||||
|
integrity sha512-bKQqIpew7mmIGNRlxW6Zli/QVyc3zikpGzCa797B/tRnD9OtHvZ/ts8sYXV+Ilj9u3QRaUEM8xrjgd1gwm1BpQ==
|
||||||
|
dependencies:
|
||||||
|
"@types/chai" "*"
|
||||||
|
"@types/sinon" "*"
|
||||||
|
|
||||||
|
"@types/sinon@*", "@types/sinon@^10.0.6":
|
||||||
|
version "10.0.6"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/sinon/-/sinon-10.0.6.tgz#bc3faff5154e6ecb69b797d311b7cf0c1b523a1d"
|
||||||
|
integrity sha512-6EF+wzMWvBNeGrfP3Nx60hhx+FfwSg1JJBLAAP/IdIUq0EYkqCYf70VT3PhuhPX9eLD+Dp+lNdpb/ZeHG8Yezg==
|
||||||
|
dependencies:
|
||||||
|
"@sinonjs/fake-timers" "^7.1.0"
|
||||||
|
|
||||||
"@types/trusted-types@^2.0.2":
|
"@types/trusted-types@^2.0.2":
|
||||||
version "2.0.2"
|
version "2.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/@types/trusted-types/-/trusted-types-2.0.2.tgz#fc25ad9943bcac11cceb8168db4f275e0e72e756"
|
resolved "https://registry.yarnpkg.com/@types/trusted-types/-/trusted-types-2.0.2.tgz#fc25ad9943bcac11cceb8168db4f275e0e72e756"
|
||||||
@ -718,7 +804,7 @@
|
|||||||
chrome-launcher "^0.15.0"
|
chrome-launcher "^0.15.0"
|
||||||
puppeteer-core "^11.0.0"
|
puppeteer-core "^11.0.0"
|
||||||
|
|
||||||
"@web/test-runner-commands@^0.5.10":
|
"@web/test-runner-commands@^0.5.10", "@web/test-runner-commands@^0.5.7":
|
||||||
version "0.5.13"
|
version "0.5.13"
|
||||||
resolved "https://registry.yarnpkg.com/@web/test-runner-commands/-/test-runner-commands-0.5.13.tgz#57ea472c00ee2ada99eb9bb5a0371200922707c2"
|
resolved "https://registry.yarnpkg.com/@web/test-runner-commands/-/test-runner-commands-0.5.13.tgz#57ea472c00ee2ada99eb9bb5a0371200922707c2"
|
||||||
integrity sha512-FXnpUU89ALbRlh9mgBd7CbSn5uzNtr8gvnQZPOvGLDAJ7twGvZdUJEAisPygYx2BLPSFl3/Mre8pH8zshJb8UQ==
|
integrity sha512-FXnpUU89ALbRlh9mgBd7CbSn5uzNtr8gvnQZPOvGLDAJ7twGvZdUJEAisPygYx2BLPSFl3/Mre8pH8zshJb8UQ==
|
||||||
@ -928,6 +1014,11 @@
|
|||||||
"@webassemblyjs/ast" "1.11.1"
|
"@webassemblyjs/ast" "1.11.1"
|
||||||
"@xtuc/long" "4.2.2"
|
"@xtuc/long" "4.2.2"
|
||||||
|
|
||||||
|
"@webcomponents/scoped-custom-element-registry@^0.0.3":
|
||||||
|
version "0.0.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/@webcomponents/scoped-custom-element-registry/-/scoped-custom-element-registry-0.0.3.tgz#774591a886b0b0e4914717273ba53fd8d5657522"
|
||||||
|
integrity sha512-lpSzgDCGbM99dytb3+J3Suo4+Bk1E13MPnWB42JK8GwxSAxFz+tC7TTv2hhDSIE2IirGNKNKCf3m08ecu6eAsQ==
|
||||||
|
|
||||||
"@webpack-cli/configtest@^1.0.4":
|
"@webpack-cli/configtest@^1.0.4":
|
||||||
version "1.0.4"
|
version "1.0.4"
|
||||||
resolved "https://registry.yarnpkg.com/@webpack-cli/configtest/-/configtest-1.0.4.tgz#f03ce6311c0883a83d04569e2c03c6238316d2aa"
|
resolved "https://registry.yarnpkg.com/@webpack-cli/configtest/-/configtest-1.0.4.tgz#f03ce6311c0883a83d04569e2c03c6238316d2aa"
|
||||||
@ -1145,6 +1236,11 @@ autoprefixer@^10.3.6:
|
|||||||
normalize-range "^0.1.2"
|
normalize-range "^0.1.2"
|
||||||
postcss-value-parser "^4.1.0"
|
postcss-value-parser "^4.1.0"
|
||||||
|
|
||||||
|
axe-core@^4.3.3:
|
||||||
|
version "4.3.5"
|
||||||
|
resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-4.3.5.tgz#78d6911ba317a8262bfee292aeafcc1e04b49cc5"
|
||||||
|
integrity sha512-WKTW1+xAzhMS5dJsxWkliixlO/PqC4VhmO9T4juNYcaTg9jzWiJsou6m5pxWYGfigWbwzJWeFY6z47a+4neRXA==
|
||||||
|
|
||||||
axios@^0.22.0:
|
axios@^0.22.0:
|
||||||
version "0.22.0"
|
version "0.22.0"
|
||||||
resolved "https://registry.yarnpkg.com/axios/-/axios-0.22.0.tgz#bf702c41fb50fbca4539589d839a077117b79b25"
|
resolved "https://registry.yarnpkg.com/axios/-/axios-0.22.0.tgz#bf702c41fb50fbca4539589d839a077117b79b25"
|
||||||
@ -1314,6 +1410,13 @@ caniuse-lite@^1.0.30001260, caniuse-lite@^1.0.30001261:
|
|||||||
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001263.tgz#7ce7a6fb482a137585cbc908aaf38e90c53a16a4"
|
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001263.tgz#7ce7a6fb482a137585cbc908aaf38e90c53a16a4"
|
||||||
integrity sha512-doiV5dft6yzWO1WwU19kt8Qz8R0/8DgEziz6/9n2FxUasteZNwNNYSmJO3GLBH8lCVE73AB1RPDPAeYbcO5Cvw==
|
integrity sha512-doiV5dft6yzWO1WwU19kt8Qz8R0/8DgEziz6/9n2FxUasteZNwNNYSmJO3GLBH8lCVE73AB1RPDPAeYbcO5Cvw==
|
||||||
|
|
||||||
|
chai-a11y-axe@^1.3.2:
|
||||||
|
version "1.3.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/chai-a11y-axe/-/chai-a11y-axe-1.3.2.tgz#77dc5f503901fed4f6097b5b0213ddb00cc891ea"
|
||||||
|
integrity sha512-/jYczmhGUoCfEcsrkJwjecy3PJ31T9FxFdu2BDlAwR/sX1nN3L2XmuPP3tw8iYk6LPqdF7K11wwFr3yUZMv5MA==
|
||||||
|
dependencies:
|
||||||
|
axe-core "^4.3.3"
|
||||||
|
|
||||||
chalk@^2.0.0, chalk@^2.4.2:
|
chalk@^2.0.0, chalk@^2.4.2:
|
||||||
version "2.4.2"
|
version "2.4.2"
|
||||||
resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424"
|
resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424"
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user