@peerbit/react 1.1.30 → 1.1.32
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/src/lockstorage.d.ts +13 -0
- package/dist/src/lockstorage.d.ts.map +1 -1
- package/dist/src/lockstorage.js +125 -27
- package/dist/src/lockstorage.js.map +1 -1
- package/dist/src/usePeer.d.ts.map +1 -1
- package/dist/src/usePeer.js +95 -23
- package/dist/src/usePeer.js.map +1 -1
- package/dist/src/utils.d.ts +8 -0
- package/dist/src/utils.d.ts.map +1 -1
- package/dist/src/utils.js +165 -20
- package/dist/src/utils.js.map +1 -1
- package/package.json +13 -13
- package/src/lockstorage.ts +154 -32
- package/src/usePeer.tsx +119 -22
- package/src/utils.ts +219 -24
package/src/usePeer.tsx
CHANGED
|
@@ -20,8 +20,10 @@ import { v4 as uuid } from "uuid";
|
|
|
20
20
|
import { FastMutex } from "./lockstorage.ts";
|
|
21
21
|
import {
|
|
22
22
|
cookiesWhereClearedJustNow,
|
|
23
|
+
createWebLockClientId,
|
|
23
24
|
getClientId,
|
|
24
25
|
getFreeKeypair,
|
|
26
|
+
getFreeKeypairWithWebLock,
|
|
25
27
|
inIframe,
|
|
26
28
|
} from "./utils.ts";
|
|
27
29
|
|
|
@@ -245,8 +247,16 @@ export const PeerProvider = ({ config, children }: PeerProviderProps) => {
|
|
|
245
247
|
React.useEffect(() => {
|
|
246
248
|
let unmounted = false;
|
|
247
249
|
let unloadUnsubscribe: (() => void) | undefined;
|
|
250
|
+
let identityUnloadUnsubscribe: (() => void) | undefined;
|
|
248
251
|
let stopKeepAlive: (() => void) | undefined;
|
|
249
252
|
let closePeer: (() => void) | undefined;
|
|
253
|
+
let releaseIdentityLock: (() => void) | undefined;
|
|
254
|
+
const startupAbortController = new AbortController();
|
|
255
|
+
const releaseIdentity = () => {
|
|
256
|
+
const release = releaseIdentityLock;
|
|
257
|
+
releaseIdentityLock = undefined;
|
|
258
|
+
release?.();
|
|
259
|
+
};
|
|
250
260
|
|
|
251
261
|
const selected = resolveConfig(config);
|
|
252
262
|
setRuntime(selected.runtime);
|
|
@@ -344,32 +354,44 @@ export const PeerProvider = ({ config, children }: PeerProviderProps) => {
|
|
|
344
354
|
const keepAliveRef = { current: true } as { current: boolean };
|
|
345
355
|
|
|
346
356
|
const releaseFirstLock = cookiesWhereClearedJustNow();
|
|
357
|
+
const webLockManager =
|
|
358
|
+
typeof navigator.locks?.request === "function"
|
|
359
|
+
? navigator.locks
|
|
360
|
+
: undefined;
|
|
347
361
|
const sessionId = getClientId("session");
|
|
348
|
-
const mutex = new FastMutex({
|
|
362
|
+
const mutex = new FastMutex({
|
|
363
|
+
clientId: webLockManager
|
|
364
|
+
? createWebLockClientId()
|
|
365
|
+
: sessionId,
|
|
366
|
+
timeout: 1e3,
|
|
367
|
+
});
|
|
349
368
|
|
|
350
369
|
if (nodeOptions.singleton) {
|
|
370
|
+
const singletonMutex = webLockManager
|
|
371
|
+
? new FastMutex({ clientId: sessionId, timeout: 1e3 })
|
|
372
|
+
: mutex;
|
|
351
373
|
singletonLog("acquiring lock");
|
|
352
374
|
const localId = getClientId("local");
|
|
353
375
|
try {
|
|
354
376
|
const lockKey = localId + "-singleton";
|
|
355
377
|
const unsubscribeUnload = subscribeToUnload(() => {
|
|
356
378
|
keepAliveRef.current = false;
|
|
357
|
-
|
|
379
|
+
singletonMutex.release(lockKey);
|
|
358
380
|
});
|
|
359
381
|
const onVisibility = () => {
|
|
360
382
|
if (document.visibilityState === "hidden") {
|
|
361
383
|
keepAliveRef.current = false;
|
|
362
384
|
try {
|
|
363
|
-
|
|
385
|
+
singletonMutex.release(lockKey);
|
|
364
386
|
} catch {}
|
|
365
387
|
}
|
|
366
388
|
};
|
|
367
389
|
document.addEventListener("visibilitychange", onVisibility);
|
|
368
390
|
if (isInStandaloneMode()) {
|
|
369
391
|
keepAliveRef.current = false;
|
|
370
|
-
|
|
392
|
+
singletonMutex.release(lockKey);
|
|
371
393
|
}
|
|
372
|
-
await
|
|
394
|
+
await singletonMutex.lock(lockKey, () => keepAliveRef.current, {
|
|
373
395
|
replaceIfSameClient: true,
|
|
374
396
|
});
|
|
375
397
|
singletonLog("lock acquired");
|
|
@@ -385,14 +407,44 @@ export const PeerProvider = ({ config, children }: PeerProviderProps) => {
|
|
|
385
407
|
nodeId = nodeOptions.keypair;
|
|
386
408
|
} else {
|
|
387
409
|
keypairLog("acquiring lock");
|
|
388
|
-
const kp =
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
410
|
+
const kp = webLockManager
|
|
411
|
+
? await getFreeKeypairWithWebLock(
|
|
412
|
+
"",
|
|
413
|
+
mutex,
|
|
414
|
+
webLockManager,
|
|
415
|
+
() => keepAliveRef.current,
|
|
416
|
+
startupAbortController.signal,
|
|
417
|
+
)
|
|
418
|
+
: await getFreeKeypair(
|
|
419
|
+
"",
|
|
420
|
+
mutex,
|
|
421
|
+
() => keepAliveRef.current,
|
|
422
|
+
{
|
|
423
|
+
releaseFirstLock,
|
|
424
|
+
releaseLockIfSameId: true,
|
|
425
|
+
},
|
|
426
|
+
);
|
|
392
427
|
keypairLog("lock acquired", { index: kp.index });
|
|
393
|
-
|
|
428
|
+
const webLockRelease = (kp as { release?: () => void }).release;
|
|
429
|
+
releaseIdentityLock = webLockRelease
|
|
430
|
+
? webLockRelease
|
|
431
|
+
: () => mutex.release(kp.path);
|
|
432
|
+
if (unmounted) {
|
|
433
|
+
releaseIdentity();
|
|
434
|
+
return;
|
|
435
|
+
}
|
|
436
|
+
identityUnloadUnsubscribe = subscribeToUnload(() => {
|
|
394
437
|
keepAliveRef.current = false;
|
|
395
|
-
|
|
438
|
+
if (!webLockManager) {
|
|
439
|
+
releaseIdentity();
|
|
440
|
+
}
|
|
441
|
+
if (closePeer) {
|
|
442
|
+
closePeer();
|
|
443
|
+
} else {
|
|
444
|
+
startupAbortController.abort(
|
|
445
|
+
new DOMException("Page unloaded", "AbortError"),
|
|
446
|
+
);
|
|
447
|
+
}
|
|
396
448
|
});
|
|
397
449
|
nodeId = kp.key;
|
|
398
450
|
setTabIndex(kp.index);
|
|
@@ -403,16 +455,42 @@ export const PeerProvider = ({ config, children }: PeerProviderProps) => {
|
|
|
403
455
|
|
|
404
456
|
let directory: string | undefined;
|
|
405
457
|
if (!nodeOptions.inMemory && !(await detectIncognito()).isPrivate) {
|
|
406
|
-
|
|
407
|
-
|
|
458
|
+
const storage = navigator.storage as StorageManager & {
|
|
459
|
+
getDirectory?: () => Promise<FileSystemDirectoryHandle>;
|
|
460
|
+
};
|
|
461
|
+
let persistedValue = false;
|
|
462
|
+
if (typeof storage?.persist === "function") {
|
|
463
|
+
storageLog("requesting persist");
|
|
464
|
+
try {
|
|
465
|
+
persistedValue = await storage.persist();
|
|
466
|
+
} catch (error) {
|
|
467
|
+
console.warn(
|
|
468
|
+
"Failed to request protection from browser storage eviction.",
|
|
469
|
+
error,
|
|
470
|
+
);
|
|
471
|
+
}
|
|
472
|
+
}
|
|
408
473
|
setPersisted(persistedValue);
|
|
409
474
|
persistedResolved = persistedValue;
|
|
410
|
-
if (
|
|
475
|
+
if (typeof storage?.getDirectory === "function") {
|
|
476
|
+
try {
|
|
477
|
+
await storage.getDirectory();
|
|
478
|
+
directory = `./repo/${peerId.toString()}/`;
|
|
479
|
+
if (!persistedValue) {
|
|
480
|
+
console.warn(
|
|
481
|
+
"Browser storage is not protected from eviction; continuing with OPFS-backed storage.",
|
|
482
|
+
);
|
|
483
|
+
}
|
|
484
|
+
} catch (error) {
|
|
485
|
+
console.error(
|
|
486
|
+
"Origin-private file system storage could not be opened; falling back to in-memory storage.",
|
|
487
|
+
error,
|
|
488
|
+
);
|
|
489
|
+
}
|
|
490
|
+
} else {
|
|
411
491
|
console.error(
|
|
412
|
-
"
|
|
492
|
+
"Origin-private file system storage is unavailable; falling back to in-memory storage.",
|
|
413
493
|
);
|
|
414
|
-
} else {
|
|
415
|
-
directory = `./repo/${peerId.toString()}/`;
|
|
416
494
|
}
|
|
417
495
|
}
|
|
418
496
|
|
|
@@ -449,9 +527,18 @@ export const PeerProvider = ({ config, children }: PeerProviderProps) => {
|
|
|
449
527
|
});
|
|
450
528
|
|
|
451
529
|
newPeer = created as unknown as PeerbitLike;
|
|
530
|
+
let stopping: Promise<void> | undefined;
|
|
452
531
|
closePeer = () => {
|
|
453
|
-
|
|
454
|
-
|
|
532
|
+
stopping ??= Promise.resolve()
|
|
533
|
+
.then(() => (created as any)?.stop?.())
|
|
534
|
+
.then(() => {
|
|
535
|
+
keepAliveRef.current = false;
|
|
536
|
+
releaseIdentity();
|
|
537
|
+
})
|
|
538
|
+
.catch((error) => {
|
|
539
|
+
stopping = undefined;
|
|
540
|
+
clientLog("stop failed; retaining identity lock", error);
|
|
541
|
+
});
|
|
455
542
|
};
|
|
456
543
|
|
|
457
544
|
(window as any).__peerInfo = {
|
|
@@ -584,9 +671,7 @@ export const PeerProvider = ({ config, children }: PeerProviderProps) => {
|
|
|
584
671
|
}
|
|
585
672
|
|
|
586
673
|
if (unmounted) {
|
|
587
|
-
|
|
588
|
-
await (created as any)?.stop?.();
|
|
589
|
-
} catch {}
|
|
674
|
+
closePeer();
|
|
590
675
|
return;
|
|
591
676
|
}
|
|
592
677
|
setPeer(newPeer);
|
|
@@ -597,6 +682,14 @@ export const PeerProvider = ({ config, children }: PeerProviderProps) => {
|
|
|
597
682
|
try {
|
|
598
683
|
await fn();
|
|
599
684
|
} catch (error: any) {
|
|
685
|
+
if (closePeer) {
|
|
686
|
+
closePeer();
|
|
687
|
+
} else {
|
|
688
|
+
releaseIdentity();
|
|
689
|
+
}
|
|
690
|
+
if (unmounted) {
|
|
691
|
+
return;
|
|
692
|
+
}
|
|
600
693
|
setError(error);
|
|
601
694
|
setConnectionState("failed");
|
|
602
695
|
setLoading(false);
|
|
@@ -607,7 +700,11 @@ export const PeerProvider = ({ config, children }: PeerProviderProps) => {
|
|
|
607
700
|
setPromise(p);
|
|
608
701
|
return () => {
|
|
609
702
|
unmounted = true;
|
|
703
|
+
startupAbortController.abort(
|
|
704
|
+
new DOMException("PeerProvider unmounted", "AbortError"),
|
|
705
|
+
);
|
|
610
706
|
unloadUnsubscribe?.();
|
|
707
|
+
identityUnloadUnsubscribe?.();
|
|
611
708
|
closePeer?.();
|
|
612
709
|
};
|
|
613
710
|
}, [config]);
|
package/src/utils.ts
CHANGED
|
@@ -2,10 +2,12 @@ import { deserialize, serialize } from "@dao-xyz/borsh";
|
|
|
2
2
|
import { Ed25519Keypair, fromBase64, toBase64 } from "@peerbit/crypto";
|
|
3
3
|
import sodium from "libsodium-wrappers";
|
|
4
4
|
import { v4 as uuid } from "uuid";
|
|
5
|
-
import { FastMutex } from "./lockstorage.ts";
|
|
5
|
+
import { acquireWebLock, FastMutex, type WebLockLease } from "./lockstorage.ts";
|
|
6
6
|
|
|
7
7
|
const CLIENT_ID_STORAGE_KEY = "CLIENT_ID";
|
|
8
8
|
const ID_COUNTER_KEY = "idc/";
|
|
9
|
+
export const WEB_LOCK_CLIENT_ID_PREFIX = "@peerbit/react:web-lock/v1:";
|
|
10
|
+
const WEB_LOCK_NAME_PREFIX = "@peerbit/react:";
|
|
9
11
|
|
|
10
12
|
const getKeyId = (prefix: string, id: number) => `${prefix}/${id}`;
|
|
11
13
|
|
|
@@ -33,6 +35,121 @@ export const releaseKey = (path: string, lock: FastMutex) => {
|
|
|
33
35
|
lock.release(path);
|
|
34
36
|
};
|
|
35
37
|
|
|
38
|
+
export const createWebLockClientId = () =>
|
|
39
|
+
`${WEB_LOCK_CLIENT_ID_PREFIX}${uuid()}`;
|
|
40
|
+
|
|
41
|
+
const isWebLockClientId = (clientId: string) =>
|
|
42
|
+
clientId.startsWith(WEB_LOCK_CLIENT_ID_PREFIX);
|
|
43
|
+
|
|
44
|
+
const getSelectionLockName = (id: string) =>
|
|
45
|
+
`${WEB_LOCK_NAME_PREFIX}keypair-selection:${JSON.stringify(id)}`;
|
|
46
|
+
|
|
47
|
+
const getKeypairLockName = (id: string, index: number) =>
|
|
48
|
+
`${WEB_LOCK_NAME_PREFIX}keypair:${JSON.stringify([id, index])}`;
|
|
49
|
+
|
|
50
|
+
const getAbortReason = (signal: AbortSignal) =>
|
|
51
|
+
signal.reason ?? new DOMException("Aborted", "AbortError");
|
|
52
|
+
|
|
53
|
+
const throwIfAborted = (signal?: AbortSignal) => {
|
|
54
|
+
if (signal?.aborted) {
|
|
55
|
+
throw getAbortReason(signal);
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
const waitForLegacyCounter = async (
|
|
60
|
+
key: string,
|
|
61
|
+
lock: FastMutex,
|
|
62
|
+
signal?: AbortSignal,
|
|
63
|
+
) => {
|
|
64
|
+
const startedAt = Date.now();
|
|
65
|
+
while (true) {
|
|
66
|
+
throwIfAborted(signal);
|
|
67
|
+
const owners = lock.getLockedOwners(key);
|
|
68
|
+
if (owners.length === 0) {
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
if (owners.every(isWebLockClientId)) {
|
|
72
|
+
lock.releaseIfOwnedBy(key, isWebLockClientId);
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
if (Date.now() - startedAt >= lock.timeout * 2 + 50) {
|
|
76
|
+
throw new Error("Timed out waiting for the legacy key counter lock");
|
|
77
|
+
}
|
|
78
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
const acquirePinnedKeypairLock = async ({
|
|
83
|
+
id,
|
|
84
|
+
index,
|
|
85
|
+
lock,
|
|
86
|
+
lockManager,
|
|
87
|
+
lockCondition,
|
|
88
|
+
signal,
|
|
89
|
+
}: {
|
|
90
|
+
id: string;
|
|
91
|
+
index: number;
|
|
92
|
+
lock: FastMutex;
|
|
93
|
+
lockManager: LockManager;
|
|
94
|
+
lockCondition: () => boolean;
|
|
95
|
+
signal?: AbortSignal;
|
|
96
|
+
}): Promise<WebLockLease | undefined> => {
|
|
97
|
+
const webLock = await acquireWebLock(
|
|
98
|
+
lockManager,
|
|
99
|
+
getKeypairLockName(id, index),
|
|
100
|
+
{ ifAvailable: true, signal },
|
|
101
|
+
);
|
|
102
|
+
if (!webLock) {
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const key = getKeyId(id, index);
|
|
107
|
+
const owners = lock.getLockedOwners(key);
|
|
108
|
+
if (owners.some((owner) => !isWebLockClientId(owner))) {
|
|
109
|
+
webLock.release();
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
lock.releaseIfOwnedBy(key, isWebLockClientId);
|
|
113
|
+
|
|
114
|
+
try {
|
|
115
|
+
await lock.lock(key, lockCondition);
|
|
116
|
+
throwIfAborted(signal);
|
|
117
|
+
lock.pin(key);
|
|
118
|
+
} catch (error) {
|
|
119
|
+
try {
|
|
120
|
+
lock.release(key);
|
|
121
|
+
} finally {
|
|
122
|
+
webLock.release();
|
|
123
|
+
}
|
|
124
|
+
throw error;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
let released = false;
|
|
128
|
+
const release = () => {
|
|
129
|
+
if (released) return;
|
|
130
|
+
released = true;
|
|
131
|
+
signal?.removeEventListener("abort", release);
|
|
132
|
+
try {
|
|
133
|
+
lock.release(key);
|
|
134
|
+
} finally {
|
|
135
|
+
webLock.release();
|
|
136
|
+
}
|
|
137
|
+
};
|
|
138
|
+
signal?.addEventListener("abort", release, { once: true });
|
|
139
|
+
if (signal?.aborted) {
|
|
140
|
+
release();
|
|
141
|
+
throw getAbortReason(signal);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
return {
|
|
145
|
+
release,
|
|
146
|
+
detachAbort: () => {
|
|
147
|
+
signal?.removeEventListener("abort", release);
|
|
148
|
+
webLock.detachAbort();
|
|
149
|
+
},
|
|
150
|
+
};
|
|
151
|
+
};
|
|
152
|
+
|
|
36
153
|
export const getFreeKeypair = async (
|
|
37
154
|
id = "",
|
|
38
155
|
lock: FastMutex,
|
|
@@ -45,34 +162,112 @@ export const getFreeKeypair = async (
|
|
|
45
162
|
await sodium.ready;
|
|
46
163
|
const idCounterKey = ID_COUNTER_KEY + id;
|
|
47
164
|
await lock.lock(idCounterKey, () => true);
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
if (
|
|
54
|
-
(
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
165
|
+
try {
|
|
166
|
+
const idCounter = JSON.parse(localStorage.getItem(idCounterKey) || "0");
|
|
167
|
+
for (let i = 0; i < 10000; i++) {
|
|
168
|
+
const key = getKeyId(id, i);
|
|
169
|
+
const lockedInfo = lock.getLockedInfo(key);
|
|
170
|
+
if (lockedInfo) {
|
|
171
|
+
if (
|
|
172
|
+
lockedInfo === lock.clientId &&
|
|
173
|
+
options?.releaseLockIfSameId
|
|
174
|
+
) {
|
|
175
|
+
lock.release(key);
|
|
176
|
+
} else if (options?.releaseFirstLock) {
|
|
177
|
+
lock.releaseIfOwnedBy(key, () => true);
|
|
178
|
+
} else {
|
|
179
|
+
continue;
|
|
180
|
+
}
|
|
60
181
|
}
|
|
182
|
+
|
|
183
|
+
await lock.lock(key, lockCondition);
|
|
184
|
+
localStorage.setItem(
|
|
185
|
+
idCounterKey,
|
|
186
|
+
JSON.stringify(Math.max(idCounter, i + 1)),
|
|
187
|
+
);
|
|
188
|
+
return {
|
|
189
|
+
index: i,
|
|
190
|
+
path: key,
|
|
191
|
+
key: await getKeypair(key),
|
|
192
|
+
};
|
|
61
193
|
}
|
|
194
|
+
throw new Error("Failed to resolve key");
|
|
195
|
+
} finally {
|
|
196
|
+
lock.release(idCounterKey);
|
|
197
|
+
}
|
|
198
|
+
};
|
|
62
199
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
200
|
+
export const getFreeKeypairWithWebLock = async (
|
|
201
|
+
id: string,
|
|
202
|
+
lock: FastMutex,
|
|
203
|
+
lockManager: LockManager,
|
|
204
|
+
lockCondition: () => boolean = () => true,
|
|
205
|
+
signal?: AbortSignal,
|
|
206
|
+
) => {
|
|
207
|
+
if (!isWebLockClientId(lock.clientId)) {
|
|
208
|
+
throw new Error(
|
|
209
|
+
"Web Lock key allocation requires a tagged mutex client ID",
|
|
67
210
|
);
|
|
68
|
-
await lock.release(idCounterKey);
|
|
69
|
-
return {
|
|
70
|
-
index: i,
|
|
71
|
-
path: key,
|
|
72
|
-
key: await getKeypair(key),
|
|
73
|
-
};
|
|
74
211
|
}
|
|
75
|
-
|
|
212
|
+
await sodium.ready;
|
|
213
|
+
throwIfAborted(signal);
|
|
214
|
+
|
|
215
|
+
const selectionLock = await acquireWebLock(
|
|
216
|
+
lockManager,
|
|
217
|
+
getSelectionLockName(id),
|
|
218
|
+
{ signal },
|
|
219
|
+
);
|
|
220
|
+
if (!selectionLock) {
|
|
221
|
+
throw new Error("Failed to acquire the keypair selection lock");
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
const idCounterKey = ID_COUNTER_KEY + id;
|
|
225
|
+
let counterLocked = false;
|
|
226
|
+
try {
|
|
227
|
+
await waitForLegacyCounter(idCounterKey, lock, signal);
|
|
228
|
+
await lock.lock(idCounterKey, () => true);
|
|
229
|
+
counterLocked = true;
|
|
230
|
+
const idCounter = JSON.parse(localStorage.getItem(idCounterKey) || "0");
|
|
231
|
+
|
|
232
|
+
for (let i = 0; i < 10000; i++) {
|
|
233
|
+
throwIfAborted(signal);
|
|
234
|
+
const selected = await acquirePinnedKeypairLock({
|
|
235
|
+
id,
|
|
236
|
+
index: i,
|
|
237
|
+
lock,
|
|
238
|
+
lockManager,
|
|
239
|
+
lockCondition,
|
|
240
|
+
signal,
|
|
241
|
+
});
|
|
242
|
+
if (!selected) {
|
|
243
|
+
continue;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
const path = getKeyId(id, i);
|
|
247
|
+
try {
|
|
248
|
+
const key = await getKeypair(path);
|
|
249
|
+
throwIfAborted(signal);
|
|
250
|
+
localStorage.setItem(
|
|
251
|
+
idCounterKey,
|
|
252
|
+
JSON.stringify(Math.max(idCounter, i + 1)),
|
|
253
|
+
);
|
|
254
|
+
selected.detachAbort();
|
|
255
|
+
return { index: i, path, key, release: selected.release };
|
|
256
|
+
} catch (error) {
|
|
257
|
+
selected.release();
|
|
258
|
+
throw error;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
throw new Error("Failed to resolve key");
|
|
262
|
+
} finally {
|
|
263
|
+
try {
|
|
264
|
+
if (counterLocked) {
|
|
265
|
+
lock.release(idCounterKey);
|
|
266
|
+
}
|
|
267
|
+
} finally {
|
|
268
|
+
selectionLock.release();
|
|
269
|
+
}
|
|
270
|
+
}
|
|
76
271
|
};
|
|
77
272
|
|
|
78
273
|
export const getAllKeyPairs = async (id = "") => {
|