Add progressive web app companion for cross-platform access
Vite + TypeScript PWA that mirrors the Android app's core features: - Pre-processed shelter data (build-time UTM33N→WGS84 conversion) - Leaflet map with shelter markers, user location, and offline tiles - Canvas compass arrow (ported from DirectionArrowView.kt) - IndexedDB shelter cache with 7-day staleness check - Service worker with CacheFirst tiles and precached app shell - i18n for en, nb, nn (ported from Android strings.xml) - iOS/Android compass handling with low-pass filter - Respects user map interaction (no auto-snap on pan/zoom) - Build revision cache-breaker for reliable SW updates Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
46365b713b
commit
e8428de775
12051 changed files with 1799735 additions and 0 deletions
532
pwa/node_modules/@vitest/mocker/dist/chunk-mocker.js
generated
vendored
Normal file
532
pwa/node_modules/@vitest/mocker/dist/chunk-mocker.js
generated
vendored
Normal file
|
|
@ -0,0 +1,532 @@
|
|||
import { mockObject } from './index.js';
|
||||
import { M as MockerRegistry, R as RedirectedModule, A as AutomockedModule } from './chunk-registry.js';
|
||||
import { e as extname, j as join } from './chunk-pathe.ff20891b.js';
|
||||
|
||||
function createSimpleStackTrace(options) {
|
||||
const { message = "$$stack trace error", stackTraceLimit = 1 } = options || {};
|
||||
const limit = Error.stackTraceLimit;
|
||||
const prepareStackTrace = Error.prepareStackTrace;
|
||||
Error.stackTraceLimit = stackTraceLimit;
|
||||
Error.prepareStackTrace = (e) => e.stack;
|
||||
const err = new Error(message);
|
||||
const stackTrace = err.stack || "";
|
||||
Error.prepareStackTrace = prepareStackTrace;
|
||||
Error.stackTraceLimit = limit;
|
||||
return stackTrace;
|
||||
}
|
||||
|
||||
const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
||||
const intToChar = new Uint8Array(64);
|
||||
const charToInt = new Uint8Array(128);
|
||||
for (let i = 0; i < chars.length; i++) {
|
||||
const c = chars.charCodeAt(i);
|
||||
intToChar[i] = c;
|
||||
charToInt[c] = i;
|
||||
}
|
||||
var UrlType;
|
||||
(function(UrlType2) {
|
||||
UrlType2[UrlType2["Empty"] = 1] = "Empty";
|
||||
UrlType2[UrlType2["Hash"] = 2] = "Hash";
|
||||
UrlType2[UrlType2["Query"] = 3] = "Query";
|
||||
UrlType2[UrlType2["RelativePath"] = 4] = "RelativePath";
|
||||
UrlType2[UrlType2["AbsolutePath"] = 5] = "AbsolutePath";
|
||||
UrlType2[UrlType2["SchemeRelative"] = 6] = "SchemeRelative";
|
||||
UrlType2[UrlType2["Absolute"] = 7] = "Absolute";
|
||||
})(UrlType || (UrlType = {}));
|
||||
const _DRIVE_LETTER_START_RE = /^[A-Za-z]:\//;
|
||||
function normalizeWindowsPath(input = "") {
|
||||
if (!input) {
|
||||
return input;
|
||||
}
|
||||
return input.replace(/\\/g, "/").replace(_DRIVE_LETTER_START_RE, (r) => r.toUpperCase());
|
||||
}
|
||||
const _IS_ABSOLUTE_RE = /^[/\\](?![/\\])|^[/\\]{2}(?!\.)|^[A-Za-z]:[/\\]/;
|
||||
function cwd() {
|
||||
if (typeof process !== "undefined" && typeof process.cwd === "function") {
|
||||
return process.cwd().replace(/\\/g, "/");
|
||||
}
|
||||
return "/";
|
||||
}
|
||||
const resolve = function(...arguments_) {
|
||||
arguments_ = arguments_.map((argument) => normalizeWindowsPath(argument));
|
||||
let resolvedPath = "";
|
||||
let resolvedAbsolute = false;
|
||||
for (let index = arguments_.length - 1; index >= -1 && !resolvedAbsolute; index--) {
|
||||
const path = index >= 0 ? arguments_[index] : cwd();
|
||||
if (!path || path.length === 0) {
|
||||
continue;
|
||||
}
|
||||
resolvedPath = `${path}/${resolvedPath}`;
|
||||
resolvedAbsolute = isAbsolute(path);
|
||||
}
|
||||
resolvedPath = normalizeString(resolvedPath, !resolvedAbsolute);
|
||||
if (resolvedAbsolute && !isAbsolute(resolvedPath)) {
|
||||
return `/${resolvedPath}`;
|
||||
}
|
||||
return resolvedPath.length > 0 ? resolvedPath : ".";
|
||||
};
|
||||
function normalizeString(path, allowAboveRoot) {
|
||||
let res = "";
|
||||
let lastSegmentLength = 0;
|
||||
let lastSlash = -1;
|
||||
let dots = 0;
|
||||
let char = null;
|
||||
for (let index = 0; index <= path.length; ++index) {
|
||||
if (index < path.length) {
|
||||
char = path[index];
|
||||
} else if (char === "/") {
|
||||
break;
|
||||
} else {
|
||||
char = "/";
|
||||
}
|
||||
if (char === "/") {
|
||||
if (lastSlash === index - 1 || dots === 1) ;
|
||||
else if (dots === 2) {
|
||||
if (res.length < 2 || lastSegmentLength !== 2 || res[res.length - 1] !== "." || res[res.length - 2] !== ".") {
|
||||
if (res.length > 2) {
|
||||
const lastSlashIndex = res.lastIndexOf("/");
|
||||
if (lastSlashIndex === -1) {
|
||||
res = "";
|
||||
lastSegmentLength = 0;
|
||||
} else {
|
||||
res = res.slice(0, lastSlashIndex);
|
||||
lastSegmentLength = res.length - 1 - res.lastIndexOf("/");
|
||||
}
|
||||
lastSlash = index;
|
||||
dots = 0;
|
||||
continue;
|
||||
} else if (res.length > 0) {
|
||||
res = "";
|
||||
lastSegmentLength = 0;
|
||||
lastSlash = index;
|
||||
dots = 0;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (allowAboveRoot) {
|
||||
res += res.length > 0 ? "/.." : "..";
|
||||
lastSegmentLength = 2;
|
||||
}
|
||||
} else {
|
||||
if (res.length > 0) {
|
||||
res += `/${path.slice(lastSlash + 1, index)}`;
|
||||
} else {
|
||||
res = path.slice(lastSlash + 1, index);
|
||||
}
|
||||
lastSegmentLength = index - lastSlash - 1;
|
||||
}
|
||||
lastSlash = index;
|
||||
dots = 0;
|
||||
} else if (char === "." && dots !== -1) {
|
||||
++dots;
|
||||
} else {
|
||||
dots = -1;
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
const isAbsolute = function(p) {
|
||||
return _IS_ABSOLUTE_RE.test(p);
|
||||
};
|
||||
const CHROME_IE_STACK_REGEXP = /^\s*at .*(?:\S:\d+|\(native\))/m;
|
||||
const SAFARI_NATIVE_CODE_REGEXP = /^(?:eval@)?(?:\[native code\])?$/;
|
||||
function extractLocation(urlLike) {
|
||||
if (!urlLike.includes(":")) {
|
||||
return [urlLike];
|
||||
}
|
||||
const regExp = /(.+?)(?::(\d+))?(?::(\d+))?$/;
|
||||
const parts = regExp.exec(urlLike.replace(/^\(|\)$/g, ""));
|
||||
if (!parts) {
|
||||
return [urlLike];
|
||||
}
|
||||
let url = parts[1];
|
||||
if (url.startsWith("async ")) {
|
||||
url = url.slice(6);
|
||||
}
|
||||
if (url.startsWith("http:") || url.startsWith("https:")) {
|
||||
const urlObj = new URL(url);
|
||||
url = urlObj.pathname;
|
||||
}
|
||||
if (url.startsWith("/@fs/")) {
|
||||
const isWindows = /^\/@fs\/[a-zA-Z]:\//.test(url);
|
||||
url = url.slice(isWindows ? 5 : 4);
|
||||
}
|
||||
return [url, parts[2] || void 0, parts[3] || void 0];
|
||||
}
|
||||
function parseSingleFFOrSafariStack(raw) {
|
||||
let line = raw.trim();
|
||||
if (SAFARI_NATIVE_CODE_REGEXP.test(line)) {
|
||||
return null;
|
||||
}
|
||||
if (line.includes(" > eval")) {
|
||||
line = line.replace(
|
||||
/ line (\d+)(?: > eval line \d+)* > eval:\d+:\d+/g,
|
||||
":$1"
|
||||
);
|
||||
}
|
||||
if (!line.includes("@") && !line.includes(":")) {
|
||||
return null;
|
||||
}
|
||||
const functionNameRegex = /((.*".+"[^@]*)?[^@]*)(@)/;
|
||||
const matches = line.match(functionNameRegex);
|
||||
const functionName = matches && matches[1] ? matches[1] : void 0;
|
||||
const [url, lineNumber, columnNumber] = extractLocation(
|
||||
line.replace(functionNameRegex, "")
|
||||
);
|
||||
if (!url || !lineNumber || !columnNumber) {
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
file: url,
|
||||
method: functionName || "",
|
||||
line: Number.parseInt(lineNumber),
|
||||
column: Number.parseInt(columnNumber)
|
||||
};
|
||||
}
|
||||
function parseSingleStack(raw) {
|
||||
const line = raw.trim();
|
||||
if (!CHROME_IE_STACK_REGEXP.test(line)) {
|
||||
return parseSingleFFOrSafariStack(line);
|
||||
}
|
||||
return parseSingleV8Stack(line);
|
||||
}
|
||||
function parseSingleV8Stack(raw) {
|
||||
let line = raw.trim();
|
||||
if (!CHROME_IE_STACK_REGEXP.test(line)) {
|
||||
return null;
|
||||
}
|
||||
if (line.includes("(eval ")) {
|
||||
line = line.replace(/eval code/g, "eval").replace(/(\(eval at [^()]*)|(,.*$)/g, "");
|
||||
}
|
||||
let sanitizedLine = line.replace(/^\s+/, "").replace(/\(eval code/g, "(").replace(/^.*?\s+/, "");
|
||||
const location = sanitizedLine.match(/ (\(.+\)$)/);
|
||||
sanitizedLine = location ? sanitizedLine.replace(location[0], "") : sanitizedLine;
|
||||
const [url, lineNumber, columnNumber] = extractLocation(
|
||||
location ? location[1] : sanitizedLine
|
||||
);
|
||||
let method = location && sanitizedLine || "";
|
||||
let file = url && ["eval", "<anonymous>"].includes(url) ? void 0 : url;
|
||||
if (!file || !lineNumber || !columnNumber) {
|
||||
return null;
|
||||
}
|
||||
if (method.startsWith("async ")) {
|
||||
method = method.slice(6);
|
||||
}
|
||||
if (file.startsWith("file://")) {
|
||||
file = file.slice(7);
|
||||
}
|
||||
file = resolve(file);
|
||||
if (method) {
|
||||
method = method.replace(/__vite_ssr_import_\d+__\./g, "");
|
||||
}
|
||||
return {
|
||||
method,
|
||||
file,
|
||||
line: Number.parseInt(lineNumber),
|
||||
column: Number.parseInt(columnNumber)
|
||||
};
|
||||
}
|
||||
|
||||
function createCompilerHints(options) {
|
||||
const globalThisAccessor = (options == null ? void 0 : options.globalThisKey) || "__vitest_mocker__";
|
||||
function _mocker() {
|
||||
return typeof globalThis[globalThisAccessor] !== "undefined" ? globalThis[globalThisAccessor] : new Proxy(
|
||||
{},
|
||||
{
|
||||
get(_, name) {
|
||||
throw new Error(
|
||||
`Vitest mocker was not initialized in this environment. vi.${String(name)}() is forbidden.`
|
||||
);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
return {
|
||||
hoisted(factory) {
|
||||
if (typeof factory !== "function") {
|
||||
throw new TypeError(
|
||||
`vi.hoisted() expects a function, but received a ${typeof factory}`
|
||||
);
|
||||
}
|
||||
return factory();
|
||||
},
|
||||
mock(path, factory) {
|
||||
if (typeof path !== "string") {
|
||||
throw new TypeError(
|
||||
`vi.mock() expects a string path, but received a ${typeof path}`
|
||||
);
|
||||
}
|
||||
const importer = getImporter("mock");
|
||||
_mocker().queueMock(
|
||||
path,
|
||||
importer,
|
||||
typeof factory === "function" ? () => factory(
|
||||
() => _mocker().importActual(
|
||||
path,
|
||||
importer
|
||||
)
|
||||
) : factory
|
||||
);
|
||||
},
|
||||
unmock(path) {
|
||||
if (typeof path !== "string") {
|
||||
throw new TypeError(
|
||||
`vi.unmock() expects a string path, but received a ${typeof path}`
|
||||
);
|
||||
}
|
||||
_mocker().queueUnmock(path, getImporter("unmock"));
|
||||
},
|
||||
doMock(path, factory) {
|
||||
if (typeof path !== "string") {
|
||||
throw new TypeError(
|
||||
`vi.doMock() expects a string path, but received a ${typeof path}`
|
||||
);
|
||||
}
|
||||
const importer = getImporter("doMock");
|
||||
_mocker().queueMock(
|
||||
path,
|
||||
importer,
|
||||
typeof factory === "function" ? () => factory(
|
||||
() => _mocker().importActual(
|
||||
path,
|
||||
importer
|
||||
)
|
||||
) : factory
|
||||
);
|
||||
},
|
||||
doUnmock(path) {
|
||||
if (typeof path !== "string") {
|
||||
throw new TypeError(
|
||||
`vi.doUnmock() expects a string path, but received a ${typeof path}`
|
||||
);
|
||||
}
|
||||
_mocker().queueUnmock(path, getImporter("doUnmock"));
|
||||
},
|
||||
async importActual(path) {
|
||||
return _mocker().importActual(
|
||||
path,
|
||||
getImporter("importActual")
|
||||
);
|
||||
},
|
||||
async importMock(path) {
|
||||
return _mocker().importMock(path, getImporter("importMock"));
|
||||
}
|
||||
};
|
||||
}
|
||||
function getImporter(name) {
|
||||
const stackTrace = /* @__PURE__ */ createSimpleStackTrace({ stackTraceLimit: 5 });
|
||||
const stackArray = stackTrace.split("\n");
|
||||
const importerStackIndex = stackArray.findIndex((stack2) => {
|
||||
return stack2.includes(` at Object.${name}`) || stack2.includes(`${name}@`);
|
||||
});
|
||||
const stack = /* @__PURE__ */ parseSingleStack(stackArray[importerStackIndex + 1]);
|
||||
return (stack == null ? void 0 : stack.file) || "";
|
||||
}
|
||||
|
||||
const hot = import.meta.hot || {
|
||||
on: warn,
|
||||
off: warn,
|
||||
send: warn
|
||||
};
|
||||
function warn() {
|
||||
console.warn("Vitest mocker cannot work if Vite didn't establish WS connection.");
|
||||
}
|
||||
function rpc(event, data) {
|
||||
hot.send(event, data);
|
||||
return new Promise((resolve, reject) => {
|
||||
const timeout = setTimeout(() => {
|
||||
reject(new Error(`Failed to resolve ${event} in time`));
|
||||
}, 5e3);
|
||||
hot.on(`${event}:result`, function r(data2) {
|
||||
resolve(data2);
|
||||
clearTimeout(timeout);
|
||||
hot.off("vitest:mocks:resolvedId:result", r);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
const { now } = Date;
|
||||
class ModuleMocker {
|
||||
constructor(interceptor, rpc, spyOn, config) {
|
||||
this.interceptor = interceptor;
|
||||
this.rpc = rpc;
|
||||
this.spyOn = spyOn;
|
||||
this.config = config;
|
||||
}
|
||||
registry = new MockerRegistry();
|
||||
queue = /* @__PURE__ */ new Set();
|
||||
mockedIds = /* @__PURE__ */ new Set();
|
||||
async prepare() {
|
||||
if (!this.queue.size) {
|
||||
return;
|
||||
}
|
||||
await Promise.all([...this.queue.values()]);
|
||||
}
|
||||
async resolveFactoryModule(id) {
|
||||
const mock = this.registry.get(id);
|
||||
if (!mock || mock.type !== "manual") {
|
||||
throw new Error(`Mock ${id} wasn't registered. This is probably a Vitest error. Please, open a new issue with reproduction.`);
|
||||
}
|
||||
const result = await mock.resolve();
|
||||
return result;
|
||||
}
|
||||
getFactoryModule(id) {
|
||||
const mock = this.registry.get(id);
|
||||
if (!mock || mock.type !== "manual") {
|
||||
throw new Error(`Mock ${id} wasn't registered. This is probably a Vitest error. Please, open a new issue with reproduction.`);
|
||||
}
|
||||
if (!mock.cache) {
|
||||
throw new Error(`Mock ${id} wasn't resolved. This is probably a Vitest error. Please, open a new issue with reproduction.`);
|
||||
}
|
||||
return mock.cache;
|
||||
}
|
||||
async invalidate() {
|
||||
const ids = Array.from(this.mockedIds);
|
||||
if (!ids.length) {
|
||||
return;
|
||||
}
|
||||
await this.rpc.invalidate(ids);
|
||||
this.interceptor.invalidate();
|
||||
this.registry.clear();
|
||||
}
|
||||
async importActual(id, importer) {
|
||||
const resolved = await this.rpc.resolveId(id, importer);
|
||||
if (resolved == null) {
|
||||
throw new Error(
|
||||
`[vitest] Cannot resolve "${id}" imported from "${importer}"`
|
||||
);
|
||||
}
|
||||
const ext = extname(resolved.id);
|
||||
const url = new URL(resolved.url, location.href);
|
||||
const query = `_vitest_original&ext${ext}`;
|
||||
const actualUrl = `${url.pathname}${url.search ? `${url.search}&${query}` : `?${query}`}${url.hash}`;
|
||||
return this.wrapDynamicImport(() => import(
|
||||
/* @vite-ignore */
|
||||
actualUrl
|
||||
)).then((mod) => {
|
||||
if (!resolved.optimized || typeof mod.default === "undefined") {
|
||||
return mod;
|
||||
}
|
||||
const m = mod.default;
|
||||
return (m == null ? void 0 : m.__esModule) ? m : { ...typeof m === "object" && !Array.isArray(m) || typeof m === "function" ? m : {}, default: m };
|
||||
});
|
||||
}
|
||||
async importMock(rawId, importer) {
|
||||
await this.prepare();
|
||||
const { resolvedId, redirectUrl } = await this.rpc.resolveMock(
|
||||
rawId,
|
||||
importer,
|
||||
{ mock: "auto" }
|
||||
);
|
||||
const mockUrl = this.resolveMockPath(cleanVersion(resolvedId));
|
||||
let mock = this.registry.get(mockUrl);
|
||||
if (!mock) {
|
||||
if (redirectUrl) {
|
||||
const resolvedRedirect = new URL(this.resolveMockPath(cleanVersion(redirectUrl)), location.href).toString();
|
||||
mock = new RedirectedModule(rawId, mockUrl, resolvedRedirect);
|
||||
} else {
|
||||
mock = new AutomockedModule(rawId, mockUrl);
|
||||
}
|
||||
}
|
||||
if (mock.type === "manual") {
|
||||
return await mock.resolve();
|
||||
}
|
||||
if (mock.type === "automock" || mock.type === "autospy") {
|
||||
const url = new URL(`/@id/${resolvedId}`, location.href);
|
||||
const query = url.search ? `${url.search}&t=${now()}` : `?t=${now()}`;
|
||||
const moduleObject = await import(
|
||||
/* @vite-ignore */
|
||||
`${url.pathname}${query}&mock=${mock.type}${url.hash}`
|
||||
);
|
||||
return this.mockObject(moduleObject, mock.type);
|
||||
}
|
||||
return import(
|
||||
/* @vite-ignore */
|
||||
mock.redirect
|
||||
);
|
||||
}
|
||||
mockObject(object, moduleType = "automock") {
|
||||
return mockObject({
|
||||
globalConstructors: {
|
||||
Object,
|
||||
Function,
|
||||
Array,
|
||||
Map,
|
||||
RegExp
|
||||
},
|
||||
spyOn: this.spyOn,
|
||||
type: moduleType
|
||||
}, object);
|
||||
}
|
||||
queueMock(rawId, importer, factoryOrOptions) {
|
||||
const promise = this.rpc.resolveMock(rawId, importer, {
|
||||
mock: typeof factoryOrOptions === "function" ? "factory" : (factoryOrOptions == null ? void 0 : factoryOrOptions.spy) ? "spy" : "auto"
|
||||
}).then(async ({ redirectUrl, resolvedId, needsInterop, mockType }) => {
|
||||
const mockUrl = this.resolveMockPath(cleanVersion(resolvedId));
|
||||
this.mockedIds.add(resolvedId);
|
||||
const factory = typeof factoryOrOptions === "function" ? async () => {
|
||||
const data = await factoryOrOptions();
|
||||
return needsInterop ? { default: data } : data;
|
||||
} : void 0;
|
||||
const mockRedirect = typeof redirectUrl === "string" ? new URL(this.resolveMockPath(cleanVersion(redirectUrl)), location.href).toString() : null;
|
||||
let module;
|
||||
if (mockType === "manual") {
|
||||
module = this.registry.register("manual", rawId, mockUrl, factory);
|
||||
} else if (mockType === "autospy") {
|
||||
module = this.registry.register("autospy", rawId, mockUrl);
|
||||
} else if (mockType === "redirect") {
|
||||
module = this.registry.register("redirect", rawId, mockUrl, mockRedirect);
|
||||
} else {
|
||||
module = this.registry.register("automock", rawId, mockUrl);
|
||||
}
|
||||
await this.interceptor.register(module);
|
||||
}).finally(() => {
|
||||
this.queue.delete(promise);
|
||||
});
|
||||
this.queue.add(promise);
|
||||
}
|
||||
queueUnmock(id, importer) {
|
||||
const promise = this.rpc.resolveId(id, importer).then(async (resolved) => {
|
||||
if (!resolved) {
|
||||
return;
|
||||
}
|
||||
const mockUrl = this.resolveMockPath(cleanVersion(resolved.id));
|
||||
this.mockedIds.add(resolved.id);
|
||||
this.registry.delete(mockUrl);
|
||||
await this.interceptor.delete(mockUrl);
|
||||
}).finally(() => {
|
||||
this.queue.delete(promise);
|
||||
});
|
||||
this.queue.add(promise);
|
||||
}
|
||||
// We need to await mock registration before importing the actual module
|
||||
// In case there is a mocked module in the import chain
|
||||
wrapDynamicImport(moduleFactory) {
|
||||
if (typeof moduleFactory === "function") {
|
||||
const promise = new Promise((resolve, reject) => {
|
||||
this.prepare().finally(() => {
|
||||
moduleFactory().then(resolve, reject);
|
||||
});
|
||||
});
|
||||
return promise;
|
||||
}
|
||||
return moduleFactory;
|
||||
}
|
||||
resolveMockPath(path) {
|
||||
const config = this.config;
|
||||
const fsRoot = join("/@fs/", config.root);
|
||||
if (path.startsWith(config.root)) {
|
||||
return path.slice(config.root.length);
|
||||
}
|
||||
if (path.startsWith(fsRoot)) {
|
||||
return path.slice(fsRoot.length);
|
||||
}
|
||||
return path;
|
||||
}
|
||||
}
|
||||
const versionRegexp = /(\?|&)v=\w{8}/;
|
||||
function cleanVersion(url) {
|
||||
return url.replace(versionRegexp, "");
|
||||
}
|
||||
|
||||
export { ModuleMocker as M, createCompilerHints as c, hot as h, rpc as r };
|
||||
Loading…
Add table
Add a link
Reference in a new issue