Use localStorage with Web Storage API (#23172)

pull/23220/head^2
Wendelin 2024-12-10 11:14:15 +01:00 committed by GitHub
parent 008647aa7a
commit 8f19c0abb0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
18 changed files with 773 additions and 38 deletions

View File

@ -26,7 +26,7 @@ export class HADemoCard extends LitElement implements LovelaceCard {
@state() private _switching = false; @state() private _switching = false;
private _hidden = localStorage.hide_demo_card; private _hidden = window.localStorage.getItem("hide_demo_card");
public getCardSize() { public getCardSize() {
return this._hidden ? 0 : 2; return this._hidden ? 0 : 2;

View File

@ -122,7 +122,10 @@ class HaLandingPage extends LandingPageBaseElement {
if (language !== this.language && language) { if (language !== this.language && language) {
this.language = language; this.language = language;
try { try {
localStorage.setItem("selectedLanguage", JSON.stringify(language)); window.localStorage.setItem(
"selectedLanguage",
JSON.stringify(language)
);
} catch (err: any) { } catch (err: any) {
// Ignore // Ignore
} }

View File

@ -213,6 +213,7 @@
"gulp-rename": "2.0.0", "gulp-rename": "2.0.0",
"html-minifier-terser": "7.2.0", "html-minifier-terser": "7.2.0",
"husky": "9.1.7", "husky": "9.1.7",
"jsdom": "25.0.1",
"jszip": "3.10.1", "jszip": "3.10.1",
"lint-staged": "15.2.10", "lint-staged": "15.2.10",
"lit-analyzer": "2.0.3", "lit-analyzer": "2.0.3",

View File

@ -327,7 +327,7 @@ export class HaAuthorize extends litLocalizeLiteMixin(LitElement) {
this.language = language; this.language = language;
try { try {
localStorage.setItem("selectedLanguage", JSON.stringify(language)); window.localStorage.setItem("selectedLanguage", JSON.stringify(language));
} catch (err: any) { } catch (err: any) {
// Ignore // Ignore
} }

View File

@ -1,8 +1,6 @@
import type { AuthData } from "home-assistant-js-websocket"; import type { AuthData } from "home-assistant-js-websocket";
import { extractSearchParam } from "../url/search-params"; import { extractSearchParam } from "../url/search-params";
const storage = window.localStorage || {};
declare global { declare global {
interface Window { interface Window {
__tokenCache: { __tokenCache: {
@ -38,9 +36,15 @@ export function saveTokens(tokens: AuthData | null) {
if (tokenCache.writeEnabled) { if (tokenCache.writeEnabled) {
try { try {
storage.hassTokens = JSON.stringify(tokens); window.localStorage.setItem("hassTokens", JSON.stringify(tokens));
} catch (err: any) { } catch (err: any) {
// write failed, ignore it. Happens if storage is full or private mode. // write failed, ignore it. Happens if storage is full or private mode.
// eslint-disable-next-line no-console
console.warn(
"Failed to store tokens; Are you in private mode or is your storage full?"
);
// eslint-disable-next-line no-console
console.error("Error storing tokens:", err);
} }
} }
} }
@ -51,12 +55,11 @@ export function enableWrite() {
saveTokens(tokenCache.tokens); saveTokens(tokenCache.tokens);
} }
} }
export function loadTokens() { export function loadTokens() {
if (tokenCache.tokens === undefined) { if (tokenCache.tokens === undefined) {
try { try {
// Delete the old token cache. const tokens = window.localStorage.getItem("hassTokens");
delete storage.tokens;
const tokens = storage.hassTokens;
if (tokens) { if (tokens) {
tokenCache.tokens = JSON.parse(tokens); tokenCache.tokens = JSON.parse(tokens);
tokenCache.writeEnabled = true; tokenCache.writeEnabled = true;

View File

@ -4,10 +4,11 @@ import type { HomeAssistant, PanelInfo } from "../types";
/** Panel to show when no panel is picked. */ /** Panel to show when no panel is picked. */
export const DEFAULT_PANEL = "lovelace"; export const DEFAULT_PANEL = "lovelace";
export const getStorageDefaultPanelUrlPath = (): string => export const getStorageDefaultPanelUrlPath = (): string => {
localStorage.defaultPanel const defaultPanel = window.localStorage.getItem("defaultPanel");
? JSON.parse(localStorage.defaultPanel)
: DEFAULT_PANEL; return defaultPanel ? JSON.parse(defaultPanel) : DEFAULT_PANEL;
};
export const setDefaultPanel = ( export const setDefaultPanel = (
element: HTMLElement, element: HTMLElement,

View File

@ -482,7 +482,10 @@ class HaOnboarding extends litLocalizeLiteMixin(HassElement) {
storeState(this.hass!); storeState(this.hass!);
} else { } else {
try { try {
localStorage.setItem("selectedLanguage", JSON.stringify(language)); window.localStorage.setItem(
"selectedLanguage",
JSON.stringify(language)
);
} catch (err: any) { } catch (err: any) {
// Ignore // Ignore
} }

View File

@ -481,13 +481,14 @@ export class HaAutomationTrace extends LitElement {
if (!traceText) { if (!traceText) {
return; return;
} }
localStorage.devTrace = traceText; window.localStorage.setItem("devTrace", traceText);
this._loadLocalTrace(traceText); this._loadLocalTrace(traceText);
} }
private _loadLocalStorageTrace() { private _loadLocalStorageTrace() {
if (localStorage.devTrace) { const devTrace = window.localStorage.getItem("devTrace");
this._loadLocalTrace(localStorage.devTrace); if (devTrace) {
this._loadLocalTrace(devTrace);
} }
} }

View File

@ -487,13 +487,15 @@ export class HaScriptTrace extends LitElement {
if (!traceText) { if (!traceText) {
return; return;
} }
localStorage.devTrace = traceText;
window.localStorage.setItem("devTrace", traceText);
this._loadLocalTrace(traceText); this._loadLocalTrace(traceText);
} }
private _loadLocalStorageTrace() { private _loadLocalStorageTrace() {
if (localStorage.devTrace) { const devTrace = window.localStorage.getItem("devTrace");
this._loadLocalTrace(localStorage.devTrace); if (devTrace) {
this._loadLocalTrace(devTrace);
} }
} }

View File

@ -10,16 +10,24 @@ const STORED_STATE = [
"enableShortcuts", "enableShortcuts",
"defaultPanel", "defaultPanel",
]; ];
const STORAGE = window.localStorage || {};
export function storeState(hass: HomeAssistant) { export function storeState(hass: HomeAssistant) {
try { try {
STORED_STATE.forEach((key) => { STORED_STATE.forEach((key) => {
const value = hass[key]; const value = hass[key];
STORAGE[key] = JSON.stringify(value === undefined ? null : value); window.localStorage.setItem(
key,
JSON.stringify(value === undefined ? null : value)
);
}); });
} catch (err: any) { } catch (err: any) {
// Safari throws exception in private mode // Safari throws exception in private mode
// eslint-disable-next-line no-console
console.warn(
"Cannot store state; Are you in private mode or is your storage full?"
);
// eslint-disable-next-line no-console
console.error(err);
} }
} }
@ -27,8 +35,9 @@ export function getState() {
const state = {}; const state = {};
STORED_STATE.forEach((key) => { STORED_STATE.forEach((key) => {
if (key in STORAGE) { const storageItem = window.localStorage.getItem(key);
let value = JSON.parse(STORAGE[key]); if (storageItem !== null) {
let value = JSON.parse(storageItem);
// selectedTheme went from string to object on 20200718 // selectedTheme went from string to object on 20200718
if (key === "selectedTheme" && typeof value === "string") { if (key === "selectedTheme" && typeof value === "string") {
value = { theme: value }; value = { theme: value };
@ -44,8 +53,5 @@ export function getState() {
} }
export function clearState() { export function clearState() {
// STORAGE is an object if localStorage not available. window.localStorage.clear();
if (STORAGE.clear) {
STORAGE.clear();
}
} }

View File

@ -0,0 +1,62 @@
import { afterEach, describe, expect, test, vi } from "vitest";
let askWrite;
describe("token_storage.askWrite", () => {
afterEach(() => {
vi.resetModules();
});
test("askWrite", async () => {
vi.stubGlobal(
"window.__tokenCache",
(window.__tokenCache = {
tokens: undefined,
writeEnabled: true,
})
);
({ askWrite } = await import("../../../../src/common/auth/token_storage"));
expect(askWrite()).toBe(false);
});
test("askWrite prefilled token", async () => {
vi.stubGlobal(
"window.__tokenCache",
(window.__tokenCache = {
tokens: {
access_token: "test",
expires: 1800,
expires_in: 1800,
hassUrl: "http://localhost",
refresh_token: "refresh",
clientId: "client",
},
writeEnabled: undefined,
})
);
({ askWrite } = await import("../../../../src/common/auth/token_storage"));
expect(askWrite()).toBe(true);
});
test("askWrite prefilled token, write enabled", async () => {
vi.stubGlobal(
"window.__tokenCache",
(window.__tokenCache = {
tokens: {
access_token: "test",
expires: 1800,
expires_in: 1800,
hassUrl: "http://localhost",
refresh_token: "refresh",
clientId: "client",
},
writeEnabled: true,
})
);
({ askWrite } = await import("../../../../src/common/auth/token_storage"));
expect(askWrite()).toBe(false);
});
});

View File

@ -0,0 +1,140 @@
import { afterEach, beforeEach, describe, expect, test, vi } from "vitest";
import type { AuthData } from "home-assistant-js-websocket";
import { FallbackStorage } from "../../../test_helper/local-storage-fallback";
let saveTokens;
describe("token_storage.saveTokens", () => {
beforeEach(() => {
window.localStorage = new FallbackStorage();
});
afterEach(() => {
vi.resetModules();
vi.resetAllMocks();
});
test("saveTokens", async () => {
const tokens: AuthData = {
access_token: "test",
expires: 1800,
expires_in: 1800,
hassUrl: "http://localhost",
refresh_token: "refresh",
clientId: "client",
};
vi.stubGlobal(
"window.__tokenCache",
(window.__tokenCache = {
tokens: undefined,
writeEnabled: undefined,
})
);
({ saveTokens } = await import(
"../../../../src/common/auth/token_storage"
));
saveTokens(tokens);
expect(window.__tokenCache.tokens).toEqual(tokens);
});
test("saveTokens write enabled", async () => {
const tokens: AuthData = {
access_token: "test",
expires: 1800,
expires_in: 1800,
hassUrl: "http://localhost",
refresh_token: "refresh",
clientId: "client",
};
vi.stubGlobal(
"window.__tokenCache",
(window.__tokenCache = {
tokens: undefined,
writeEnabled: undefined,
})
);
const extractSearchParamSpy = vi.fn().mockReturnValue("true");
vi.doMock("../../../../src/common/url/search-params", () => ({
extractSearchParam: extractSearchParamSpy,
}));
const setItemSpy = vi.fn();
window.localStorage.setItem = setItemSpy;
({ saveTokens } = await import(
"../../../../src/common/auth/token_storage"
));
saveTokens(tokens);
expect(window.__tokenCache.tokens).toEqual(tokens);
expect(window.__tokenCache.writeEnabled).toBe(true);
expect(extractSearchParamSpy).toHaveBeenCalledOnce();
expect(extractSearchParamSpy).toHaveBeenCalledWith("storeToken");
expect(setItemSpy).toHaveBeenCalledOnce();
expect(setItemSpy).toHaveBeenCalledWith(
"hassTokens",
JSON.stringify(tokens)
);
});
test("saveTokens write enabled full storage", async () => {
const tokens: AuthData = {
access_token: "test",
expires: 1800,
expires_in: 1800,
hassUrl: "http://localhost",
refresh_token: "refresh",
clientId: "client",
};
vi.stubGlobal(
"window.__tokenCache",
(window.__tokenCache = {
tokens: undefined,
writeEnabled: true,
})
);
const extractSearchParamSpy = vi.fn();
vi.doMock("../../../../src/common/url/search-params", () => ({
extractSearchParam: extractSearchParamSpy,
}));
const setItemSpy = vi.fn(() => {
throw new Error("Full storage");
});
window.localStorage.setItem = setItemSpy;
// eslint-disable-next-line no-global-assign
console = {
warn: vi.fn(),
error: vi.fn(),
} as unknown as Console;
({ saveTokens } = await import(
"../../../../src/common/auth/token_storage"
));
saveTokens(tokens);
expect(window.__tokenCache.tokens).toEqual(tokens);
expect(window.__tokenCache.writeEnabled).toBe(true);
expect(extractSearchParamSpy).toBeCalledTimes(0);
expect(setItemSpy).toHaveBeenCalledOnce();
expect(setItemSpy).toHaveBeenCalledWith(
"hassTokens",
JSON.stringify(tokens)
);
// eslint-disable-next-line no-console
expect(console.warn).toHaveBeenCalledOnce();
// eslint-disable-next-line no-console
expect(console.warn).toHaveBeenCalledWith(
"Failed to store tokens; Are you in private mode or is your storage full?"
);
// eslint-disable-next-line no-console
expect(console.error).toHaveBeenCalledOnce();
});
});

View File

@ -0,0 +1,110 @@
import { describe, it, expect, test, vi, afterEach, beforeEach } from "vitest";
import type { AuthData } from "home-assistant-js-websocket";
import { FallbackStorage } from "../../../test_helper/local-storage-fallback";
describe("token_storage", () => {
beforeEach(() => {
vi.stubGlobal(
"window.__tokenCache",
(window.__tokenCache = {
tokens: undefined,
writeEnabled: undefined,
})
);
window.localStorage = new FallbackStorage();
});
afterEach(() => {
vi.resetAllMocks();
vi.resetModules();
});
test("initialize tokenCache", async () => {
vi.stubGlobal(
"window.__tokenCache",
(window.__tokenCache = undefined as any)
);
await import("../../../../src/common/auth/token_storage");
expect(window.__tokenCache).toEqual({
tokens: undefined,
writeEnabled: undefined,
});
});
test("should load tokens", async () => {
const tokens: AuthData = {
access_token: "test",
expires: 1800,
expires_in: 1800,
hassUrl: "http://localhost",
refresh_token: "refresh",
clientId: "client",
};
const getItemSpy = vi.fn(() => JSON.stringify(tokens));
window.localStorage.getItem = getItemSpy;
const { loadTokens } = await import(
"../../../../src/common/auth/token_storage"
);
const loadedTokens = loadTokens();
expect(loadedTokens).toEqual(tokens);
expect(window.__tokenCache.tokens).toEqual(tokens);
expect(window.__tokenCache.writeEnabled).toBe(true);
expect(getItemSpy).toHaveBeenCalledOnce();
expect(getItemSpy).toHaveBeenCalledWith("hassTokens");
});
test("should load null tokens", async () => {
const getItemSpy = vi.fn(() => "hello");
window.localStorage.getItem = getItemSpy;
const { loadTokens } = await import(
"../../../../src/common/auth/token_storage"
);
const loadedTokens = loadTokens();
expect(loadedTokens).toEqual(null);
expect(window.__tokenCache.tokens).toEqual(null);
expect(window.__tokenCache.writeEnabled).toBe(undefined);
expect(getItemSpy).toHaveBeenCalledOnce();
expect(getItemSpy).toHaveBeenCalledWith("hassTokens");
});
it("should enable write", async () => {
const { enableWrite } = await import(
"../../../../src/common/auth/token_storage"
);
enableWrite();
expect(window.__tokenCache.writeEnabled).toBe(true);
});
it("should enable write with tokens", async () => {
vi.stubGlobal(
"window.__tokenCache",
(window.__tokenCache = {
tokens: "testToken" as any,
})
);
const setItemSpy = vi.fn();
window.localStorage.setItem = setItemSpy;
const { enableWrite } = await import(
"../../../../src/common/auth/token_storage"
);
enableWrite();
expect(window.__tokenCache.writeEnabled).toBe(true);
expect(setItemSpy).toHaveBeenCalledOnce();
expect(setItemSpy).toHaveBeenCalledWith(
"hassTokens",
JSON.stringify("testToken")
);
});
});

View File

@ -0,0 +1,52 @@
import { describe, test, expect, beforeEach } from "vitest";
import { FallbackStorage } from "./local-storage-fallback";
describe("FallbackStorage", () => {
let storage;
beforeEach(() => {
storage = new FallbackStorage();
});
test("should set and get an item", () => {
storage.setItem("key1", "value1");
expect(storage.getItem("key1")).toBe("value1");
});
test("should return null for non-existing item", () => {
expect(storage.getItem("nonExistingKey")).toBeNull();
});
test("should remove an item", () => {
storage.setItem("key2", "value2");
storage.removeItem("key2");
expect(storage.getItem("key2")).toBeNull();
});
test("should clear all items", () => {
storage.setItem("key3", "value3");
storage.setItem("key4", "value4");
storage.clear();
expect(storage.getItem("key3")).toBeNull();
expect(storage.getItem("key4")).toBeNull();
});
test("should return the correct key for an index", () => {
storage.setItem("key5", "value5");
storage.setItem("key6", "value6");
expect(storage.key(0)).toBe("key5");
expect(storage.key(1)).toBe("key6");
});
test("should throw TypeError if key method is called without arguments", () => {
expect(() => storage.key()).toThrow(TypeError);
});
test("should return the correct length", () => {
storage.setItem("key7", "value7");
storage.setItem("key8", "value8");
expect(storage.length).toBe(2);
storage.removeItem("key7");
expect(storage.length).toBe(1);
});
});

View File

@ -0,0 +1,38 @@
export class FallbackStorage implements Storage {
private valuesMap = new Map();
getItem(key) {
const stringKey = String(key);
if (this.valuesMap.has(key)) {
return String(this.valuesMap.get(stringKey));
}
return null;
}
setItem(key, val) {
this.valuesMap.set(String(key), String(val));
}
removeItem(key) {
this.valuesMap.delete(key);
}
clear() {
this.valuesMap.clear();
}
key(i) {
if (arguments.length === 0) {
// this is a TypeError implemented on Chrome, Firefox throws Not enough arguments to Storage.key.
throw new TypeError(
"Failed to execute 'key' on 'Storage': 1 argument required, but only 0 present."
);
}
const arr = Array.from(this.valuesMap.keys());
return arr[i];
}
get length() {
return this.valuesMap.size;
}
}

View File

@ -0,0 +1,113 @@
import { describe, expect, afterEach, vi, test, beforeEach } from "vitest";
import type { HomeAssistant } from "../../src/types";
import { FallbackStorage } from "../test_helper/local-storage-fallback";
describe("ha-pref-storage", () => {
const mockHass = {
dockedSidebar: "auto",
selectedTheme: { theme: "default" },
unknownKey: "unknownValue",
};
beforeEach(() => {
window.localStorage = new FallbackStorage();
});
afterEach(() => {
vi.resetModules();
vi.resetAllMocks();
});
test("storeState", async () => {
const { storeState } = await import("../../src/util/ha-pref-storage");
window.localStorage.setItem = vi.fn();
storeState(mockHass as unknown as HomeAssistant);
expect(window.localStorage.setItem).toHaveBeenCalledTimes(8);
expect(window.localStorage.setItem).toHaveBeenCalledWith(
"dockedSidebar",
JSON.stringify("auto")
);
expect(window.localStorage.setItem).toHaveBeenCalledWith(
"selectedTheme",
JSON.stringify({ theme: "default" })
);
expect(window.localStorage.setItem).toHaveBeenCalledWith(
"selectedLanguage",
JSON.stringify(null)
);
expect(window.localStorage.setItem).not.toHaveBeenCalledWith(
"unknownKey",
JSON.stringify("unknownValue")
);
});
test("storeState fails", async () => {
const { storeState } = await import("../../src/util/ha-pref-storage");
window.localStorage.setItem = vi.fn((key) => {
if (key === "selectedTheme") {
throw new Error("Test error");
}
});
// eslint-disable-next-line no-global-assign
console = {
warn: vi.fn(),
error: vi.fn(),
} as unknown as Console;
storeState(mockHass as unknown as HomeAssistant);
expect(window.localStorage.setItem).toHaveBeenCalledTimes(2);
expect(window.localStorage.setItem).toHaveBeenCalledWith(
"dockedSidebar",
JSON.stringify("auto")
);
expect(window.localStorage.setItem).toHaveBeenCalledWith(
"selectedTheme",
JSON.stringify({ theme: "default" })
);
expect(window.localStorage.setItem).not.toHaveBeenCalledWith(
"selectedLanguage",
JSON.stringify(null)
);
// eslint-disable-next-line no-console
expect(console.warn).toHaveBeenCalledOnce();
// eslint-disable-next-line no-console
expect(console.warn).toHaveBeenCalledWith(
"Cannot store state; Are you in private mode or is your storage full?"
);
// eslint-disable-next-line no-console
expect(console.error).toHaveBeenCalledOnce();
});
test("getState", async () => {
const { getState } = await import("../../src/util/ha-pref-storage");
window.localStorage.setItem("selectedTheme", JSON.stringify("test"));
window.localStorage.setItem("dockedSidebar", JSON.stringify(true));
window.localStorage.setItem("selectedLanguage", JSON.stringify("german"));
// should not be in state
window.localStorage.setItem("testEntry", JSON.stringify("this is a test"));
const state = getState();
expect(state).toEqual({
dockedSidebar: "docked",
selectedTheme: { theme: "test" },
selectedLanguage: "german",
});
});
test("clearState", async () => {
const { clearState } = await import("../../src/util/ha-pref-storage");
window.localStorage.setItem("test", "test");
expect(window.localStorage.length).toEqual(1);
clearState();
expect(window.localStorage.length).toEqual(0);
});
});

View File

@ -2,13 +2,21 @@ import { defineConfig } from "vitest/config";
export default defineConfig({ export default defineConfig({
test: { test: {
environment: "jsdom", // to run in browser-like environment
env: { env: {
TZ: "Etc/UTC", TZ: "Etc/UTC",
IS_TEST: "true", IS_TEST: "true",
}, },
setupFiles: ["./test/setup.ts"], setupFiles: ["./test/setup.ts"],
coverage: { coverage: {
include: ["src/data/**/*", "src/common/**/*"], include: [
"src/data/**/*",
"src/common/**/*",
"src/external_app/**/*",
"src/hassio/**/*",
"src/panels/**/*",
"src/util/**/*",
],
reporter: ["text", "html"], reporter: ["text", "html"],
provider: "v8", provider: "v8",
reportsDirectory: "test/coverage", reportsDirectory: "test/coverage",

210
yarn.lock
View File

@ -6888,6 +6888,15 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"cssstyle@npm:^4.1.0":
version: 4.1.0
resolution: "cssstyle@npm:4.1.0"
dependencies:
rrweb-cssom: "npm:^0.7.1"
checksum: 10/8ca9e2d1f1b24f93bb5f3f20a7a1e271e58060957880e985ee55614e196a798ffab309ec6bac105af8a439a6764546761813835ebb7f929d60823637ee838a8f
languageName: node
linkType: hard
"csstype@npm:^3.1.0": "csstype@npm:^3.1.0":
version: 3.1.3 version: 3.1.3
resolution: "csstype@npm:3.1.3" resolution: "csstype@npm:3.1.3"
@ -6895,6 +6904,16 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"data-urls@npm:^5.0.0":
version: 5.0.0
resolution: "data-urls@npm:5.0.0"
dependencies:
whatwg-mimetype: "npm:^4.0.0"
whatwg-url: "npm:^14.0.0"
checksum: 10/5c40568c31b02641a70204ff233bc4e42d33717485d074244a98661e5f2a1e80e38fe05a5755dfaf2ee549f2ab509d6a3af2a85f4b2ad2c984e5d176695eaf46
languageName: node
linkType: hard
"data-view-buffer@npm:^1.0.1": "data-view-buffer@npm:^1.0.1":
version: 1.0.1 version: 1.0.1
resolution: "data-view-buffer@npm:1.0.1" resolution: "data-view-buffer@npm:1.0.1"
@ -6995,6 +7014,13 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"decimal.js@npm:^10.4.3":
version: 10.4.3
resolution: "decimal.js@npm:10.4.3"
checksum: 10/de663a7bc4d368e3877db95fcd5c87b965569b58d16cdc4258c063d231ca7118748738df17cd638f7e9dd0be8e34cec08d7234b20f1f2a756a52fc5a38b188d0
languageName: node
linkType: hard
"deep-clone-simple@npm:1.1.1": "deep-clone-simple@npm:1.1.1":
version: 1.1.1 version: 1.1.1
resolution: "deep-clone-simple@npm:1.1.1" resolution: "deep-clone-simple@npm:1.1.1"
@ -9240,6 +9266,7 @@ __metadata:
idb-keyval: "npm:6.2.1" idb-keyval: "npm:6.2.1"
intl-messageformat: "npm:10.7.7" intl-messageformat: "npm:10.7.7"
js-yaml: "npm:4.1.0" js-yaml: "npm:4.1.0"
jsdom: "npm:25.0.1"
jszip: "npm:3.10.1" jszip: "npm:3.10.1"
leaflet: "npm:1.9.4" leaflet: "npm:1.9.4"
leaflet-draw: "patch:leaflet-draw@npm%3A1.0.4#./.yarn/patches/leaflet-draw-npm-1.0.4-0ca0ebcf65.patch" leaflet-draw: "patch:leaflet-draw@npm%3A1.0.4#./.yarn/patches/leaflet-draw-npm-1.0.4-0ca0ebcf65.patch"
@ -9328,6 +9355,15 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"html-encoding-sniffer@npm:^4.0.0":
version: 4.0.0
resolution: "html-encoding-sniffer@npm:4.0.0"
dependencies:
whatwg-encoding: "npm:^3.1.1"
checksum: 10/e86efd493293a5671b8239bd099d42128433bb3c7b0fdc7819282ef8e118a21f5dead0ad6f358e024a4e5c84f17ebb7a9b36075220fac0a6222b207248bede6f
languageName: node
linkType: hard
"html-entities@npm:^2.4.0": "html-entities@npm:^2.4.0":
version: 2.5.2 version: 2.5.2
resolution: "html-entities@npm:2.5.2" resolution: "html-entities@npm:2.5.2"
@ -9422,7 +9458,7 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"http-proxy-agent@npm:^7.0.0": "http-proxy-agent@npm:^7.0.0, http-proxy-agent@npm:^7.0.2":
version: 7.0.2 version: 7.0.2
resolution: "http-proxy-agent@npm:7.0.2" resolution: "http-proxy-agent@npm:7.0.2"
dependencies: dependencies:
@ -9461,7 +9497,7 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"https-proxy-agent@npm:^7.0.1": "https-proxy-agent@npm:^7.0.1, https-proxy-agent@npm:^7.0.5":
version: 7.0.5 version: 7.0.5
resolution: "https-proxy-agent@npm:7.0.5" resolution: "https-proxy-agent@npm:7.0.5"
dependencies: dependencies:
@ -9510,7 +9546,7 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"iconv-lite@npm:^0.6.2, iconv-lite@npm:^0.6.3": "iconv-lite@npm:0.6.3, iconv-lite@npm:^0.6.2, iconv-lite@npm:^0.6.3":
version: 0.6.3 version: 0.6.3
resolution: "iconv-lite@npm:0.6.3" resolution: "iconv-lite@npm:0.6.3"
dependencies: dependencies:
@ -9949,7 +9985,7 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"is-potential-custom-element-name@npm:^1.0.0": "is-potential-custom-element-name@npm:^1.0.0, is-potential-custom-element-name@npm:^1.0.1":
version: 1.0.1 version: 1.0.1
resolution: "is-potential-custom-element-name@npm:1.0.1" resolution: "is-potential-custom-element-name@npm:1.0.1"
checksum: 10/ced7bbbb6433a5b684af581872afe0e1767e2d1146b2207ca0068a648fb5cab9d898495d1ac0583524faaf24ca98176a7d9876363097c2d14fee6dd324f3a1ab checksum: 10/ced7bbbb6433a5b684af581872afe0e1767e2d1146b2207ca0068a648fb5cab9d898495d1ac0583524faaf24ca98176a7d9876363097c2d14fee6dd324f3a1ab
@ -10255,6 +10291,40 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"jsdom@npm:25.0.1":
version: 25.0.1
resolution: "jsdom@npm:25.0.1"
dependencies:
cssstyle: "npm:^4.1.0"
data-urls: "npm:^5.0.0"
decimal.js: "npm:^10.4.3"
form-data: "npm:^4.0.0"
html-encoding-sniffer: "npm:^4.0.0"
http-proxy-agent: "npm:^7.0.2"
https-proxy-agent: "npm:^7.0.5"
is-potential-custom-element-name: "npm:^1.0.1"
nwsapi: "npm:^2.2.12"
parse5: "npm:^7.1.2"
rrweb-cssom: "npm:^0.7.1"
saxes: "npm:^6.0.0"
symbol-tree: "npm:^3.2.4"
tough-cookie: "npm:^5.0.0"
w3c-xmlserializer: "npm:^5.0.0"
webidl-conversions: "npm:^7.0.0"
whatwg-encoding: "npm:^3.1.1"
whatwg-mimetype: "npm:^4.0.0"
whatwg-url: "npm:^14.0.0"
ws: "npm:^8.18.0"
xml-name-validator: "npm:^5.0.0"
peerDependencies:
canvas: ^2.11.2
peerDependenciesMeta:
canvas:
optional: true
checksum: 10/e6bf7250ddd2fbcf68da0ea041a0dc63545dc4bf77fa3ff40a46ae45b1dac1ca55b87574ab904d1f8baeeb547c52cec493a22f545d7d413b320011f41150ec49
languageName: node
linkType: hard
"jsesc@npm:^3.0.2, jsesc@npm:~3.0.2": "jsesc@npm:^3.0.2, jsesc@npm:~3.0.2":
version: 3.0.2 version: 3.0.2
resolution: "jsesc@npm:3.0.2" resolution: "jsesc@npm:3.0.2"
@ -11389,6 +11459,13 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"nwsapi@npm:^2.2.12":
version: 2.2.16
resolution: "nwsapi@npm:2.2.16"
checksum: 10/1e5e086cdd4ca4a45f414d37f49bf0ca81d84ed31c6871ac68f531917d2910845db61f77c6d844430dc90fda202d43fce9603024e74038675de95229eb834dba
languageName: node
linkType: hard
"object-assign@npm:^4": "object-assign@npm:^4":
version: 4.1.1 version: 4.1.1
resolution: "object-assign@npm:4.1.1" resolution: "object-assign@npm:4.1.1"
@ -12220,7 +12297,7 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"punycode@npm:2.3.1, punycode@npm:^2.1.0": "punycode@npm:2.3.1, punycode@npm:^2.1.0, punycode@npm:^2.3.1":
version: 2.3.1 version: 2.3.1
resolution: "punycode@npm:2.3.1" resolution: "punycode@npm:2.3.1"
checksum: 10/febdc4362bead22f9e2608ff0171713230b57aff9dddc1c273aa2a651fbd366f94b7d6a71d78342a7c0819906750351ca7f2edd26ea41b626d87d6a13d1bd059 checksum: 10/febdc4362bead22f9e2608ff0171713230b57aff9dddc1c273aa2a651fbd366f94b7d6a71d78342a7c0819906750351ca7f2edd26ea41b626d87d6a13d1bd059
@ -12673,8 +12750,8 @@ __metadata:
linkType: hard linkType: hard
"rollup@npm:^2.43.1": "rollup@npm:^2.43.1":
version: 2.79.2 version: 2.79.1
resolution: "rollup@npm:2.79.2" resolution: "rollup@npm:2.79.1"
dependencies: dependencies:
fsevents: "npm:~2.3.2" fsevents: "npm:~2.3.2"
dependenciesMeta: dependenciesMeta:
@ -12682,7 +12759,7 @@ __metadata:
optional: true optional: true
bin: bin:
rollup: dist/bin/rollup rollup: dist/bin/rollup
checksum: 10/095ba0a82811b1866a76d826987743278db0a87c45092656986bfff490326b66187d5f9ff0c24cf8d5682bc470aa00c36654e0044d6b6335ac0c1201b8280880 checksum: 10/df087b701304432f30922bbee5f534ab189aa6938bd383b5686c03147e0d00cd1789ea10a462361326ce6b6ebe448ce272ad3f3cc40b82eeb3157df12f33663c
languageName: node languageName: node
linkType: hard linkType: hard
@ -12764,6 +12841,13 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"rrweb-cssom@npm:^0.7.1":
version: 0.7.1
resolution: "rrweb-cssom@npm:0.7.1"
checksum: 10/e80cf25c223a823921d7ab57c0ce78f5b7ebceab857b400cce99dd4913420ce679834bc5707e8ada47d062e21ad368108a9534c314dc8d72c20aa4a4fa0ed16a
languageName: node
linkType: hard
"rslog@npm:^1.2.3": "rslog@npm:^1.2.3":
version: 1.2.3 version: 1.2.3
resolution: "rslog@npm:1.2.3" resolution: "rslog@npm:1.2.3"
@ -12852,6 +12936,15 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"saxes@npm:^6.0.0":
version: 6.0.0
resolution: "saxes@npm:6.0.0"
dependencies:
xmlchars: "npm:^2.2.0"
checksum: 10/97b50daf6ca3a153e89842efa18a862e446248296622b7473c169c84c823ee8a16e4a43bac2f73f11fc8cb9168c73fbb0d73340f26552bac17970e9052367aa9
languageName: node
linkType: hard
"schema-utils@npm:^3.1.1": "schema-utils@npm:^3.1.1":
version: 3.3.0 version: 3.3.0
resolution: "schema-utils@npm:3.3.0" resolution: "schema-utils@npm:3.3.0"
@ -13721,6 +13814,13 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"symbol-tree@npm:^3.2.4":
version: 3.2.4
resolution: "symbol-tree@npm:3.2.4"
checksum: 10/c09a00aadf279d47d0c5c46ca3b6b2fbaeb45f0a184976d599637d412d3a70bbdc043ff33effe1206dea0e36e0ad226cb957112e7ce9a4bf2daedf7fa4f85c53
languageName: node
linkType: hard
"systemjs@npm:6.15.1": "systemjs@npm:6.15.1":
version: 6.15.1 version: 6.15.1
resolution: "systemjs@npm:6.15.1" resolution: "systemjs@npm:6.15.1"
@ -13966,6 +14066,24 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"tldts-core@npm:^6.1.65":
version: 6.1.65
resolution: "tldts-core@npm:6.1.65"
checksum: 10/06ae49ae935674163d806aa14abf6bb49a6099878ef3940de035c6bf577cf31b78122df02d2e82d84d0a59faf8a837235d537ee7cbf9a5b0b97b44b7a5f19799
languageName: node
linkType: hard
"tldts@npm:^6.1.32":
version: 6.1.65
resolution: "tldts@npm:6.1.65"
dependencies:
tldts-core: "npm:^6.1.65"
bin:
tldts: bin/cli.js
checksum: 10/dc8afe3ac8af4f2dc4b4307d447b6877ee5942e4b8164ae170edf034a318241ca7f9bdf89d4560204363141344959aa4d28edc532397b210532e160acbf77b6a
languageName: node
linkType: hard
"to-regex-range@npm:^5.0.1": "to-regex-range@npm:^5.0.1":
version: 5.0.1 version: 5.0.1
resolution: "to-regex-range@npm:5.0.1" resolution: "to-regex-range@npm:5.0.1"
@ -14015,6 +14133,15 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"tough-cookie@npm:^5.0.0":
version: 5.0.0
resolution: "tough-cookie@npm:5.0.0"
dependencies:
tldts: "npm:^6.1.32"
checksum: 10/a98d3846ed386e399e8b470c1eb08a6a296944246eabc55c9fe79d629bd2cdaa62f5a6572f271fe0060987906bd20468d72a219a3b4cbe51086bea48d2d677b6
languageName: node
linkType: hard
"tr46@npm:^1.0.1": "tr46@npm:^1.0.1":
version: 1.0.1 version: 1.0.1
resolution: "tr46@npm:1.0.1" resolution: "tr46@npm:1.0.1"
@ -14024,6 +14151,15 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"tr46@npm:^5.0.0":
version: 5.0.0
resolution: "tr46@npm:5.0.0"
dependencies:
punycode: "npm:^2.3.1"
checksum: 10/29155adb167d048d3c95d181f7cb5ac71948b4e8f3070ec455986e1f34634acae50ae02a3c8d448121c3afe35b76951cd46ed4c128fd80264280ca9502237a3e
languageName: node
linkType: hard
"tree-dump@npm:^1.0.1": "tree-dump@npm:^1.0.1":
version: 1.0.2 version: 1.0.2
resolution: "tree-dump@npm:1.0.2" resolution: "tree-dump@npm:1.0.2"
@ -14836,6 +14972,15 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"w3c-xmlserializer@npm:^5.0.0":
version: 5.0.0
resolution: "w3c-xmlserializer@npm:5.0.0"
dependencies:
xml-name-validator: "npm:^5.0.0"
checksum: 10/d78f59e6b4f924aa53b6dfc56949959229cae7fe05ea9374eb38d11edcec01398b7f5d7a12576bd5acc57ff446abb5c9115cd83b9d882555015437cf858d42f0
languageName: node
linkType: hard
"wbuf@npm:^1.1.0, wbuf@npm:^1.7.3": "wbuf@npm:^1.1.0, wbuf@npm:^1.7.3":
version: 1.7.3 version: 1.7.3
resolution: "wbuf@npm:1.7.3" resolution: "wbuf@npm:1.7.3"
@ -14876,6 +15021,13 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"webidl-conversions@npm:^7.0.0":
version: 7.0.0
resolution: "webidl-conversions@npm:7.0.0"
checksum: 10/4c4f65472c010eddbe648c11b977d048dd96956a625f7f8b9d64e1b30c3c1f23ea1acfd654648426ce5c743c2108a5a757c0592f02902cf7367adb7d14e67721
languageName: node
linkType: hard
"webpack-bundle-analyzer@npm:4.6.1": "webpack-bundle-analyzer@npm:4.6.1":
version: 4.6.1 version: 4.6.1
resolution: "webpack-bundle-analyzer@npm:4.6.1" resolution: "webpack-bundle-analyzer@npm:4.6.1"
@ -15035,6 +15187,32 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"whatwg-encoding@npm:^3.1.1":
version: 3.1.1
resolution: "whatwg-encoding@npm:3.1.1"
dependencies:
iconv-lite: "npm:0.6.3"
checksum: 10/bbef815eb67f91487c7f2ef96329743f5fd8357d7d62b1119237d25d41c7e452dff8197235b2d3c031365a17f61d3bb73ca49d0ed1582475aa4a670815e79534
languageName: node
linkType: hard
"whatwg-mimetype@npm:^4.0.0":
version: 4.0.0
resolution: "whatwg-mimetype@npm:4.0.0"
checksum: 10/894a618e2d90bf444b6f309f3ceb6e58cf21b2beaa00c8b333696958c4076f0c7b30b9d33413c9ffff7c5832a0a0c8569e5bb347ef44beded72aeefd0acd62e8
languageName: node
linkType: hard
"whatwg-url@npm:^14.0.0":
version: 14.1.0
resolution: "whatwg-url@npm:14.1.0"
dependencies:
tr46: "npm:^5.0.0"
webidl-conversions: "npm:^7.0.0"
checksum: 10/3afd325de6cf3a367820ce7c3566a1f78eb1409c4f27b1867c74c76dab096d26acedf49a8b9b71db53df7d806ec2e9ae9ed96990b2f7d1abe6ecf1fe753af6eb
languageName: node
linkType: hard
"whatwg-url@npm:^7.0.0": "whatwg-url@npm:^7.0.0":
version: 7.1.0 version: 7.1.0
resolution: "whatwg-url@npm:7.1.0" resolution: "whatwg-url@npm:7.1.0"
@ -15494,7 +15672,7 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"ws@npm:^8.16.0": "ws@npm:^8.16.0, ws@npm:^8.18.0":
version: 8.18.0 version: 8.18.0
resolution: "ws@npm:8.18.0" resolution: "ws@npm:8.18.0"
peerDependencies: peerDependencies:
@ -15536,6 +15714,13 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"xml-name-validator@npm:^5.0.0":
version: 5.0.0
resolution: "xml-name-validator@npm:5.0.0"
checksum: 10/43f30f3f6786e406dd665acf08cd742d5f8a46486bd72517edb04b27d1bcd1599664c2a4a99fc3f1e56a3194bff588b12f178b7972bc45c8047bdc4c3ac8d4a1
languageName: node
linkType: hard
"xml-parse-from-string@npm:^1.0.0": "xml-parse-from-string@npm:^1.0.0":
version: 1.0.1 version: 1.0.1
resolution: "xml-parse-from-string@npm:1.0.1" resolution: "xml-parse-from-string@npm:1.0.1"
@ -15560,6 +15745,13 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"xmlchars@npm:^2.2.0":
version: 2.2.0
resolution: "xmlchars@npm:2.2.0"
checksum: 10/4ad5924974efd004a47cce6acf5c0269aee0e62f9a805a426db3337af7bcbd331099df174b024ace4fb18971b8a56de386d2e73a1c4b020e3abd63a4a9b917f1
languageName: node
linkType: hard
"xss@npm:1.0.15": "xss@npm:1.0.15":
version: 1.0.15 version: 1.0.15
resolution: "xss@npm:1.0.15" resolution: "xss@npm:1.0.15"