mirror of
https://github.com/Youzini-afk/ST-Bionic-Memory-Ecology.git
synced 2026-05-15 22:30:38 +08:00
fix: harden opfs capability recovery
This commit is contained in:
108
index.js
108
index.js
@@ -1308,6 +1308,7 @@ let bmeLocalStoreCapabilitySnapshot = {
|
|||||||
reason: "unprobed",
|
reason: "unprobed",
|
||||||
};
|
};
|
||||||
let bmeLocalStoreCapabilityWarningShown = false;
|
let bmeLocalStoreCapabilityWarningShown = false;
|
||||||
|
const BME_LOCAL_STORE_CAPABILITY_FAILURE_RETRY_MS = 4000;
|
||||||
const bmeIndexedDbSnapshotCacheByChatId = new Map();
|
const bmeIndexedDbSnapshotCacheByChatId = new Map();
|
||||||
const bmeIndexedDbLoadInFlightByChatId = new Map();
|
const bmeIndexedDbLoadInFlightByChatId = new Map();
|
||||||
const bmeIndexedDbWriteInFlightByChatId = new Map();
|
const bmeIndexedDbWriteInFlightByChatId = new Map();
|
||||||
@@ -4425,23 +4426,65 @@ function isCachedIndexedDbSnapshotCompatible(snapshot = null, expectedStore = nu
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function getGraphLocalStoreCapability(forceRefresh = false) {
|
async function getGraphLocalStoreCapability(forceRefresh = false) {
|
||||||
if (!forceRefresh && bmeLocalStoreCapabilitySnapshot.checked) {
|
const settings =
|
||||||
|
arguments.length > 1 && arguments[1] && typeof arguments[1] === "object"
|
||||||
|
? arguments[1].settings || getSettings()
|
||||||
|
: getSettings();
|
||||||
|
const eagerRetry =
|
||||||
|
arguments.length > 1 &&
|
||||||
|
arguments[1] &&
|
||||||
|
typeof arguments[1] === "object" &&
|
||||||
|
arguments[1].eagerRetry === true;
|
||||||
|
const requestedMode = getRequestedGraphLocalStorageMode(settings);
|
||||||
|
const usesOpfsPreference =
|
||||||
|
requestedMode === "auto" || isGraphLocalStorageModeOpfs(requestedMode);
|
||||||
|
const capabilityReason = String(
|
||||||
|
bmeLocalStoreCapabilitySnapshot?.reason || "",
|
||||||
|
).trim();
|
||||||
|
const capabilityFailureStable =
|
||||||
|
capabilityReason === "missing-directory-handle" ||
|
||||||
|
capabilityReason === "OPFS 不可用" ||
|
||||||
|
/not.?supported/i.test(capabilityReason) ||
|
||||||
|
/missing.+getdirectory/i.test(capabilityReason);
|
||||||
|
const capabilityFailureRetryable =
|
||||||
|
usesOpfsPreference &&
|
||||||
|
bmeLocalStoreCapabilitySnapshot.checked === true &&
|
||||||
|
bmeLocalStoreCapabilitySnapshot.opfsAvailable !== true &&
|
||||||
|
capabilityFailureStable !== true;
|
||||||
|
const capabilityFailureAgeMs = Math.max(
|
||||||
|
0,
|
||||||
|
Date.now() - Number(bmeLocalStoreCapabilitySnapshot?.checkedAt || 0),
|
||||||
|
);
|
||||||
|
const shouldRetryFailedProbe =
|
||||||
|
forceRefresh !== true &&
|
||||||
|
capabilityFailureRetryable &&
|
||||||
|
(eagerRetry === true ||
|
||||||
|
capabilityFailureAgeMs >= BME_LOCAL_STORE_CAPABILITY_FAILURE_RETRY_MS);
|
||||||
|
|
||||||
|
if (
|
||||||
|
!forceRefresh &&
|
||||||
|
!shouldRetryFailedProbe &&
|
||||||
|
bmeLocalStoreCapabilitySnapshot.checked
|
||||||
|
) {
|
||||||
return bmeLocalStoreCapabilitySnapshot;
|
return bmeLocalStoreCapabilitySnapshot;
|
||||||
}
|
}
|
||||||
if (!forceRefresh && bmeLocalStoreCapabilityPromise) {
|
if (!forceRefresh && !shouldRetryFailedProbe && bmeLocalStoreCapabilityPromise) {
|
||||||
return await bmeLocalStoreCapabilityPromise;
|
return await bmeLocalStoreCapabilityPromise;
|
||||||
}
|
}
|
||||||
|
|
||||||
bmeLocalStoreCapabilityPromise = detectOpfsSupport()
|
bmeLocalStoreCapabilityPromise = detectOpfsSupport()
|
||||||
.then((result) => {
|
.then((result) => {
|
||||||
bmeLocalStoreCapabilitySnapshot = {
|
bmeLocalStoreCapabilitySnapshot = {
|
||||||
checked: true,
|
checked: true,
|
||||||
checkedAt: Date.now(),
|
checkedAt: Date.now(),
|
||||||
opfsAvailable: Boolean(result?.available),
|
opfsAvailable: Boolean(result?.available),
|
||||||
reason: String(result?.reason || (result?.available ? "ok" : "unavailable")),
|
reason: String(result?.reason || (result?.available ? "ok" : "unavailable")),
|
||||||
};
|
};
|
||||||
return bmeLocalStoreCapabilitySnapshot;
|
if (bmeLocalStoreCapabilitySnapshot.opfsAvailable) {
|
||||||
})
|
bmeLocalStoreCapabilityWarningShown = false;
|
||||||
|
}
|
||||||
|
return bmeLocalStoreCapabilitySnapshot;
|
||||||
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
bmeLocalStoreCapabilitySnapshot = {
|
bmeLocalStoreCapabilitySnapshot = {
|
||||||
checked: true,
|
checked: true,
|
||||||
@@ -4480,7 +4523,9 @@ function getPreferredGraphLocalStorePresentationSync(settings = getSettings()) {
|
|||||||
) {
|
) {
|
||||||
const requestedMode = getRequestedGraphLocalStorageMode(settings);
|
const requestedMode = getRequestedGraphLocalStorageMode(settings);
|
||||||
if (requestedMode === "auto") {
|
if (requestedMode === "auto") {
|
||||||
const capability = await getGraphLocalStoreCapability();
|
const capability = await getGraphLocalStoreCapability(false, {
|
||||||
|
settings,
|
||||||
|
});
|
||||||
return capability.opfsAvailable
|
return capability.opfsAvailable
|
||||||
? buildOpfsStorePresentation(BME_GRAPH_LOCAL_STORAGE_MODE_OPFS_PRIMARY)
|
? buildOpfsStorePresentation(BME_GRAPH_LOCAL_STORAGE_MODE_OPFS_PRIMARY)
|
||||||
: buildIndexedDbStorePresentation();
|
: buildIndexedDbStorePresentation();
|
||||||
@@ -4489,7 +4534,9 @@ function getPreferredGraphLocalStorePresentationSync(settings = getSettings()) {
|
|||||||
return buildIndexedDbStorePresentation();
|
return buildIndexedDbStorePresentation();
|
||||||
}
|
}
|
||||||
|
|
||||||
const capability = await getGraphLocalStoreCapability();
|
const capability = await getGraphLocalStoreCapability(false, {
|
||||||
|
settings,
|
||||||
|
});
|
||||||
if (capability.opfsAvailable) {
|
if (capability.opfsAvailable) {
|
||||||
return buildOpfsStorePresentation(requestedMode);
|
return buildOpfsStorePresentation(requestedMode);
|
||||||
}
|
}
|
||||||
@@ -4536,7 +4583,10 @@ async function refreshCurrentChatLocalStoreBinding(
|
|||||||
isGraphLocalStorageModeOpfs(requestedMode);
|
isGraphLocalStorageModeOpfs(requestedMode);
|
||||||
|
|
||||||
if (shouldProbeCapability) {
|
if (shouldProbeCapability) {
|
||||||
await getGraphLocalStoreCapability(forceCapabilityRefresh === true);
|
await getGraphLocalStoreCapability(forceCapabilityRefresh === true, {
|
||||||
|
settings,
|
||||||
|
eagerRetry: forceCapabilityRefresh === true,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const preferredLocalStore =
|
const preferredLocalStore =
|
||||||
@@ -4643,6 +4693,21 @@ function buildPanelOpenLocalStoreRefreshPlan(
|
|||||||
const resolvedIsOpfs = resolvedLocalStoreKey.startsWith("opfs:");
|
const resolvedIsOpfs = resolvedLocalStoreKey.startsWith("opfs:");
|
||||||
const preferredIsOpfs = preferredLocalStore.storagePrimary === "opfs";
|
const preferredIsOpfs = preferredLocalStore.storagePrimary === "opfs";
|
||||||
const capabilityUnchecked = bmeLocalStoreCapabilitySnapshot.checked !== true;
|
const capabilityUnchecked = bmeLocalStoreCapabilitySnapshot.checked !== true;
|
||||||
|
const capabilityRetryRecommended =
|
||||||
|
usesOpfsPreference &&
|
||||||
|
bmeLocalStoreCapabilitySnapshot.checked === true &&
|
||||||
|
bmeLocalStoreCapabilitySnapshot.opfsAvailable !== true &&
|
||||||
|
!(
|
||||||
|
String(bmeLocalStoreCapabilitySnapshot.reason || "") ===
|
||||||
|
"missing-directory-handle" ||
|
||||||
|
String(bmeLocalStoreCapabilitySnapshot.reason || "") === "OPFS 不可用" ||
|
||||||
|
/not.?supported/i.test(
|
||||||
|
String(bmeLocalStoreCapabilitySnapshot.reason || ""),
|
||||||
|
) ||
|
||||||
|
/missing.+getdirectory/i.test(
|
||||||
|
String(bmeLocalStoreCapabilitySnapshot.reason || ""),
|
||||||
|
)
|
||||||
|
);
|
||||||
const pendingPersist = graphPersistenceState.pendingPersist === true;
|
const pendingPersist = graphPersistenceState.pendingPersist === true;
|
||||||
const writesBlocked = graphPersistenceState.writesBlocked === true;
|
const writesBlocked = graphPersistenceState.writesBlocked === true;
|
||||||
const loadState = String(graphPersistenceState.loadState || "");
|
const loadState = String(graphPersistenceState.loadState || "");
|
||||||
@@ -4658,6 +4723,7 @@ function buildPanelOpenLocalStoreRefreshPlan(
|
|||||||
const shouldRefresh =
|
const shouldRefresh =
|
||||||
usesOpfsPreference &&
|
usesOpfsPreference &&
|
||||||
(capabilityUnchecked ||
|
(capabilityUnchecked ||
|
||||||
|
capabilityRetryRecommended ||
|
||||||
pendingPersist ||
|
pendingPersist ||
|
||||||
writesBlocked ||
|
writesBlocked ||
|
||||||
blocked ||
|
blocked ||
|
||||||
@@ -4666,6 +4732,7 @@ function buildPanelOpenLocalStoreRefreshPlan(
|
|||||||
localStoreMismatch);
|
localStoreMismatch);
|
||||||
const forceCapabilityRefresh =
|
const forceCapabilityRefresh =
|
||||||
capabilityUnchecked ||
|
capabilityUnchecked ||
|
||||||
|
capabilityRetryRecommended ||
|
||||||
pendingPersist ||
|
pendingPersist ||
|
||||||
blocked ||
|
blocked ||
|
||||||
loadingWithoutDb ||
|
loadingWithoutDb ||
|
||||||
@@ -4676,6 +4743,7 @@ function buildPanelOpenLocalStoreRefreshPlan(
|
|||||||
(pendingPersist || writesBlocked || blocked || Boolean(persistError) || localStoreMismatch);
|
(pendingPersist || writesBlocked || blocked || Boolean(persistError) || localStoreMismatch);
|
||||||
const reasons = [];
|
const reasons = [];
|
||||||
if (capabilityUnchecked) reasons.push("capability-unchecked");
|
if (capabilityUnchecked) reasons.push("capability-unchecked");
|
||||||
|
if (capabilityRetryRecommended) reasons.push("capability-retryable-failure");
|
||||||
if (pendingPersist) reasons.push("pending-persist");
|
if (pendingPersist) reasons.push("pending-persist");
|
||||||
if (writesBlocked) reasons.push("writes-blocked");
|
if (writesBlocked) reasons.push("writes-blocked");
|
||||||
if (blocked) reasons.push("load-blocked");
|
if (blocked) reasons.push("load-blocked");
|
||||||
@@ -5715,6 +5783,18 @@ async function syncBmeChatManagerWithCurrentChat(
|
|||||||
source = "unknown",
|
source = "unknown",
|
||||||
context = getContext(),
|
context = getContext(),
|
||||||
) {
|
) {
|
||||||
|
const currentSettings = getSettings();
|
||||||
|
const requestedMode = getRequestedGraphLocalStorageMode(currentSettings);
|
||||||
|
if (
|
||||||
|
requestedMode === "auto" ||
|
||||||
|
isGraphLocalStorageModeOpfs(requestedMode)
|
||||||
|
) {
|
||||||
|
await getGraphLocalStoreCapability(false, {
|
||||||
|
settings: currentSettings,
|
||||||
|
eagerRetry: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const manager = ensureBmeChatManager();
|
const manager = ensureBmeChatManager();
|
||||||
if (!manager) {
|
if (!manager) {
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -321,12 +321,52 @@ function isNotFoundError(error) {
|
|||||||
return name === "NotFoundError" || /not.?found/i.test(message);
|
return name === "NotFoundError" || /not.?found/i.test(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isTypeMismatchError(error) {
|
||||||
|
const name = String(error?.name || "");
|
||||||
|
const message = String(error?.message || "");
|
||||||
|
return (
|
||||||
|
name === "TypeMismatchError" ||
|
||||||
|
/type.?mismatch/i.test(message) ||
|
||||||
|
/different file type/i.test(message)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
async function ensureDirectoryHandle(parentHandle, name) {
|
async function ensureDirectoryHandle(parentHandle, name) {
|
||||||
return await parentHandle.getDirectoryHandle(String(name || ""), {
|
return await parentHandle.getDirectoryHandle(String(name || ""), {
|
||||||
create: true,
|
create: true,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function ensureOpfsRootDirectory(
|
||||||
|
rootDirectory,
|
||||||
|
{ repairFileConflict = false } = {},
|
||||||
|
) {
|
||||||
|
if (!rootDirectory || typeof rootDirectory.getDirectoryHandle !== "function") {
|
||||||
|
throw new Error("OPFS 根目录不可用");
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
return await ensureDirectoryHandle(rootDirectory, OPFS_ROOT_DIRECTORY_NAME);
|
||||||
|
} catch (error) {
|
||||||
|
if (!repairFileConflict || !isTypeMismatchError(error)) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
|
||||||
|
const conflictingFile = await maybeGetFileHandle(
|
||||||
|
rootDirectory,
|
||||||
|
OPFS_ROOT_DIRECTORY_NAME,
|
||||||
|
).catch(() => null);
|
||||||
|
if (!conflictingFile || typeof rootDirectory.removeEntry !== "function") {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
|
||||||
|
await rootDirectory.removeEntry(OPFS_ROOT_DIRECTORY_NAME, {
|
||||||
|
recursive: false,
|
||||||
|
});
|
||||||
|
return await ensureDirectoryHandle(rootDirectory, OPFS_ROOT_DIRECTORY_NAME);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function maybeGetFileHandle(parentHandle, name) {
|
async function maybeGetFileHandle(parentHandle, name) {
|
||||||
try {
|
try {
|
||||||
return await parentHandle.getFileHandle(String(name || ""), {
|
return await parentHandle.getFileHandle(String(name || ""), {
|
||||||
@@ -598,7 +638,9 @@ export async function detectOpfsSupport(options = {}) {
|
|||||||
reason: "missing-directory-handle",
|
reason: "missing-directory-handle",
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
await ensureDirectoryHandle(rootDirectory, OPFS_ROOT_DIRECTORY_NAME);
|
await ensureOpfsRootDirectory(rootDirectory, {
|
||||||
|
repairFileConflict: true,
|
||||||
|
});
|
||||||
return {
|
return {
|
||||||
available: true,
|
available: true,
|
||||||
reason: "ok",
|
reason: "ok",
|
||||||
@@ -1534,10 +1576,9 @@ class LegacyOpfsGraphStore {
|
|||||||
if (!rootDirectory || typeof rootDirectory.getDirectoryHandle !== "function") {
|
if (!rootDirectory || typeof rootDirectory.getDirectoryHandle !== "function") {
|
||||||
throw new Error("OPFS 根目录不可用");
|
throw new Error("OPFS 根目录不可用");
|
||||||
}
|
}
|
||||||
const opfsRoot = await ensureDirectoryHandle(
|
const opfsRoot = await ensureOpfsRootDirectory(rootDirectory, {
|
||||||
rootDirectory,
|
repairFileConflict: true,
|
||||||
OPFS_ROOT_DIRECTORY_NAME,
|
});
|
||||||
);
|
|
||||||
const chatsDirectory = await ensureDirectoryHandle(
|
const chatsDirectory = await ensureDirectoryHandle(
|
||||||
opfsRoot,
|
opfsRoot,
|
||||||
OPFS_CHATS_DIRECTORY_NAME,
|
OPFS_CHATS_DIRECTORY_NAME,
|
||||||
@@ -2810,10 +2851,9 @@ export class OpfsGraphStore {
|
|||||||
if (!rootDirectory || typeof rootDirectory.getDirectoryHandle !== "function") {
|
if (!rootDirectory || typeof rootDirectory.getDirectoryHandle !== "function") {
|
||||||
throw new Error("OPFS 根目录不可用");
|
throw new Error("OPFS 根目录不可用");
|
||||||
}
|
}
|
||||||
const opfsRoot = await ensureDirectoryHandle(
|
const opfsRoot = await ensureOpfsRootDirectory(rootDirectory, {
|
||||||
rootDirectory,
|
repairFileConflict: true,
|
||||||
OPFS_ROOT_DIRECTORY_NAME,
|
});
|
||||||
);
|
|
||||||
const chatsDirectory = await ensureDirectoryHandle(
|
const chatsDirectory = await ensureDirectoryHandle(
|
||||||
opfsRoot,
|
opfsRoot,
|
||||||
OPFS_CHATS_DIRECTORY_NAME,
|
OPFS_CHATS_DIRECTORY_NAME,
|
||||||
|
|||||||
@@ -2186,6 +2186,47 @@ result = {
|
|||||||
assert.equal(plan.reasons.includes("resolved-store-mismatch"), true);
|
assert.equal(plan.reasons.includes("resolved-store-mismatch"), true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const harness = await createGraphPersistenceHarness({
|
||||||
|
chatId: "chat-panel-open-capability-retry",
|
||||||
|
globalChatId: "chat-panel-open-capability-retry",
|
||||||
|
chatMetadata: {
|
||||||
|
integrity: "chat-panel-open-capability-retry-integrity",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
harness.runtimeContext.extension_settings[MODULE_NAME] = {
|
||||||
|
graphLocalStorageMode: "auto",
|
||||||
|
};
|
||||||
|
harness.api.setLocalStoreCapabilitySnapshot({
|
||||||
|
checked: true,
|
||||||
|
checkedAt: Date.now(),
|
||||||
|
opfsAvailable: false,
|
||||||
|
reason: "UnknownError: transient-opfs-init-failure",
|
||||||
|
});
|
||||||
|
harness.api.setGraphPersistenceState({
|
||||||
|
loadState: "loaded",
|
||||||
|
chatId: "chat-panel-open-capability-retry",
|
||||||
|
reason: "healthy",
|
||||||
|
dbReady: true,
|
||||||
|
writesBlocked: false,
|
||||||
|
pendingPersist: false,
|
||||||
|
indexedDbLastError: "",
|
||||||
|
resolvedLocalStore: "indexeddb:indexeddb",
|
||||||
|
storagePrimary: "indexeddb",
|
||||||
|
storageMode: "indexeddb",
|
||||||
|
});
|
||||||
|
|
||||||
|
const plan = harness.api.buildPanelOpenLocalStoreRefreshPlan();
|
||||||
|
|
||||||
|
assert.equal(plan.shouldRefresh, true);
|
||||||
|
assert.equal(plan.forceCapabilityRefresh, true);
|
||||||
|
assert.equal(
|
||||||
|
plan.reasons.includes("capability-retryable-failure"),
|
||||||
|
true,
|
||||||
|
"可恢复的 OPFS 探测失败应在面板打开时触发重新探测",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
const harness = await createGraphPersistenceHarness({
|
const harness = await createGraphPersistenceHarness({
|
||||||
chatId: "chat-luker-panel-open",
|
chatId: "chat-luker-panel-open",
|
||||||
|
|||||||
@@ -23,6 +23,12 @@ function createNotFoundError(message) {
|
|||||||
return error;
|
return error;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function createTypeMismatchError(message) {
|
||||||
|
const error = new Error(String(message || "Type mismatch"));
|
||||||
|
error.name = "TypeMismatchError";
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
|
||||||
class MemoryOpfsFileHandle {
|
class MemoryOpfsFileHandle {
|
||||||
constructor(parent, name) {
|
constructor(parent, name) {
|
||||||
this.parent = parent;
|
this.parent = parent;
|
||||||
@@ -71,6 +77,11 @@ class MemoryOpfsDirectoryHandle {
|
|||||||
|
|
||||||
async getDirectoryHandle(name, options = {}) {
|
async getDirectoryHandle(name, options = {}) {
|
||||||
const normalizedName = String(name || "");
|
const normalizedName = String(name || "");
|
||||||
|
if (this.files.has(normalizedName)) {
|
||||||
|
throw createTypeMismatchError(
|
||||||
|
`A file already exists for directory: ${normalizedName}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
let directory = this.directories.get(normalizedName) || null;
|
let directory = this.directories.get(normalizedName) || null;
|
||||||
if (!directory) {
|
if (!directory) {
|
||||||
if (!options.create) {
|
if (!options.create) {
|
||||||
@@ -84,6 +95,11 @@ class MemoryOpfsDirectoryHandle {
|
|||||||
|
|
||||||
async getFileHandle(name, options = {}) {
|
async getFileHandle(name, options = {}) {
|
||||||
const normalizedName = String(name || "");
|
const normalizedName = String(name || "");
|
||||||
|
if (this.directories.has(normalizedName)) {
|
||||||
|
throw createTypeMismatchError(
|
||||||
|
`A directory already exists for file: ${normalizedName}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
if (!this.files.has(normalizedName)) {
|
if (!this.files.has(normalizedName)) {
|
||||||
if (!options.create) {
|
if (!options.create) {
|
||||||
throw createNotFoundError(`File not found: ${normalizedName}`);
|
throw createNotFoundError(`File not found: ${normalizedName}`);
|
||||||
@@ -177,6 +193,16 @@ async function testDetectOpfsSupport() {
|
|||||||
assert.equal(supported.available, true);
|
assert.equal(supported.available, true);
|
||||||
assert.equal(supported.reason, "ok");
|
assert.equal(supported.reason, "ok");
|
||||||
|
|
||||||
|
const conflictedRootDirectory = createMemoryOpfsRoot();
|
||||||
|
conflictedRootDirectory.files.set("st-bme", "legacy-conflict");
|
||||||
|
const repairedConflict = await detectOpfsSupport({
|
||||||
|
rootDirectoryFactory: async () => conflictedRootDirectory,
|
||||||
|
});
|
||||||
|
assert.equal(repairedConflict.available, true);
|
||||||
|
assert.equal(repairedConflict.reason, "ok");
|
||||||
|
assert.equal(conflictedRootDirectory.files.has("st-bme"), false);
|
||||||
|
assert.ok(conflictedRootDirectory.directories.has("st-bme"));
|
||||||
|
|
||||||
const missingHandle = await detectOpfsSupport({
|
const missingHandle = await detectOpfsSupport({
|
||||||
rootDirectoryFactory: async () => ({}),
|
rootDirectoryFactory: async () => ({}),
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user