@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/dist/src/utils.js
CHANGED
|
@@ -2,9 +2,11 @@ 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.js";
|
|
5
|
+
import { acquireWebLock, FastMutex } from "./lockstorage.js";
|
|
6
6
|
const CLIENT_ID_STORAGE_KEY = "CLIENT_ID";
|
|
7
7
|
const ID_COUNTER_KEY = "idc/";
|
|
8
|
+
export const WEB_LOCK_CLIENT_ID_PREFIX = "@peerbit/react:web-lock/v1:";
|
|
9
|
+
const WEB_LOCK_NAME_PREFIX = "@peerbit/react:";
|
|
8
10
|
const getKeyId = (prefix, id) => `${prefix}/${id}`;
|
|
9
11
|
export const cookiesWhereClearedJustNow = () => {
|
|
10
12
|
const lastPersistedAt = localStorage.getItem("lastPersistedAt");
|
|
@@ -27,33 +29,176 @@ export const getClientId = (type) => {
|
|
|
27
29
|
export const releaseKey = (path, lock) => {
|
|
28
30
|
lock.release(path);
|
|
29
31
|
};
|
|
32
|
+
export const createWebLockClientId = () => `${WEB_LOCK_CLIENT_ID_PREFIX}${uuid()}`;
|
|
33
|
+
const isWebLockClientId = (clientId) => clientId.startsWith(WEB_LOCK_CLIENT_ID_PREFIX);
|
|
34
|
+
const getSelectionLockName = (id) => `${WEB_LOCK_NAME_PREFIX}keypair-selection:${JSON.stringify(id)}`;
|
|
35
|
+
const getKeypairLockName = (id, index) => `${WEB_LOCK_NAME_PREFIX}keypair:${JSON.stringify([id, index])}`;
|
|
36
|
+
const getAbortReason = (signal) => signal.reason ?? new DOMException("Aborted", "AbortError");
|
|
37
|
+
const throwIfAborted = (signal) => {
|
|
38
|
+
if (signal?.aborted) {
|
|
39
|
+
throw getAbortReason(signal);
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
const waitForLegacyCounter = async (key, lock, signal) => {
|
|
43
|
+
const startedAt = Date.now();
|
|
44
|
+
while (true) {
|
|
45
|
+
throwIfAborted(signal);
|
|
46
|
+
const owners = lock.getLockedOwners(key);
|
|
47
|
+
if (owners.length === 0) {
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
if (owners.every(isWebLockClientId)) {
|
|
51
|
+
lock.releaseIfOwnedBy(key, isWebLockClientId);
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
if (Date.now() - startedAt >= lock.timeout * 2 + 50) {
|
|
55
|
+
throw new Error("Timed out waiting for the legacy key counter lock");
|
|
56
|
+
}
|
|
57
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
const acquirePinnedKeypairLock = async ({ id, index, lock, lockManager, lockCondition, signal, }) => {
|
|
61
|
+
const webLock = await acquireWebLock(lockManager, getKeypairLockName(id, index), { ifAvailable: true, signal });
|
|
62
|
+
if (!webLock) {
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
const key = getKeyId(id, index);
|
|
66
|
+
const owners = lock.getLockedOwners(key);
|
|
67
|
+
if (owners.some((owner) => !isWebLockClientId(owner))) {
|
|
68
|
+
webLock.release();
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
lock.releaseIfOwnedBy(key, isWebLockClientId);
|
|
72
|
+
try {
|
|
73
|
+
await lock.lock(key, lockCondition);
|
|
74
|
+
throwIfAborted(signal);
|
|
75
|
+
lock.pin(key);
|
|
76
|
+
}
|
|
77
|
+
catch (error) {
|
|
78
|
+
try {
|
|
79
|
+
lock.release(key);
|
|
80
|
+
}
|
|
81
|
+
finally {
|
|
82
|
+
webLock.release();
|
|
83
|
+
}
|
|
84
|
+
throw error;
|
|
85
|
+
}
|
|
86
|
+
let released = false;
|
|
87
|
+
const release = () => {
|
|
88
|
+
if (released)
|
|
89
|
+
return;
|
|
90
|
+
released = true;
|
|
91
|
+
signal?.removeEventListener("abort", release);
|
|
92
|
+
try {
|
|
93
|
+
lock.release(key);
|
|
94
|
+
}
|
|
95
|
+
finally {
|
|
96
|
+
webLock.release();
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
signal?.addEventListener("abort", release, { once: true });
|
|
100
|
+
if (signal?.aborted) {
|
|
101
|
+
release();
|
|
102
|
+
throw getAbortReason(signal);
|
|
103
|
+
}
|
|
104
|
+
return {
|
|
105
|
+
release,
|
|
106
|
+
detachAbort: () => {
|
|
107
|
+
signal?.removeEventListener("abort", release);
|
|
108
|
+
webLock.detachAbort();
|
|
109
|
+
},
|
|
110
|
+
};
|
|
111
|
+
};
|
|
30
112
|
export const getFreeKeypair = async (id = "", lock, lockCondition = () => true, options) => {
|
|
31
113
|
await sodium.ready;
|
|
32
114
|
const idCounterKey = ID_COUNTER_KEY + id;
|
|
33
115
|
await lock.lock(idCounterKey, () => true);
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
if (
|
|
40
|
-
|
|
41
|
-
|
|
116
|
+
try {
|
|
117
|
+
const idCounter = JSON.parse(localStorage.getItem(idCounterKey) || "0");
|
|
118
|
+
for (let i = 0; i < 10000; i++) {
|
|
119
|
+
const key = getKeyId(id, i);
|
|
120
|
+
const lockedInfo = lock.getLockedInfo(key);
|
|
121
|
+
if (lockedInfo) {
|
|
122
|
+
if (lockedInfo === lock.clientId &&
|
|
123
|
+
options?.releaseLockIfSameId) {
|
|
124
|
+
lock.release(key);
|
|
125
|
+
}
|
|
126
|
+
else if (options?.releaseFirstLock) {
|
|
127
|
+
lock.releaseIfOwnedBy(key, () => true);
|
|
128
|
+
}
|
|
129
|
+
else {
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
42
132
|
}
|
|
43
|
-
|
|
133
|
+
await lock.lock(key, lockCondition);
|
|
134
|
+
localStorage.setItem(idCounterKey, JSON.stringify(Math.max(idCounter, i + 1)));
|
|
135
|
+
return {
|
|
136
|
+
index: i,
|
|
137
|
+
path: key,
|
|
138
|
+
key: await getKeypair(key),
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
throw new Error("Failed to resolve key");
|
|
142
|
+
}
|
|
143
|
+
finally {
|
|
144
|
+
lock.release(idCounterKey);
|
|
145
|
+
}
|
|
146
|
+
};
|
|
147
|
+
export const getFreeKeypairWithWebLock = async (id, lock, lockManager, lockCondition = () => true, signal) => {
|
|
148
|
+
if (!isWebLockClientId(lock.clientId)) {
|
|
149
|
+
throw new Error("Web Lock key allocation requires a tagged mutex client ID");
|
|
150
|
+
}
|
|
151
|
+
await sodium.ready;
|
|
152
|
+
throwIfAborted(signal);
|
|
153
|
+
const selectionLock = await acquireWebLock(lockManager, getSelectionLockName(id), { signal });
|
|
154
|
+
if (!selectionLock) {
|
|
155
|
+
throw new Error("Failed to acquire the keypair selection lock");
|
|
156
|
+
}
|
|
157
|
+
const idCounterKey = ID_COUNTER_KEY + id;
|
|
158
|
+
let counterLocked = false;
|
|
159
|
+
try {
|
|
160
|
+
await waitForLegacyCounter(idCounterKey, lock, signal);
|
|
161
|
+
await lock.lock(idCounterKey, () => true);
|
|
162
|
+
counterLocked = true;
|
|
163
|
+
const idCounter = JSON.parse(localStorage.getItem(idCounterKey) || "0");
|
|
164
|
+
for (let i = 0; i < 10000; i++) {
|
|
165
|
+
throwIfAborted(signal);
|
|
166
|
+
const selected = await acquirePinnedKeypairLock({
|
|
167
|
+
id,
|
|
168
|
+
index: i,
|
|
169
|
+
lock,
|
|
170
|
+
lockManager,
|
|
171
|
+
lockCondition,
|
|
172
|
+
signal,
|
|
173
|
+
});
|
|
174
|
+
if (!selected) {
|
|
44
175
|
continue;
|
|
45
176
|
}
|
|
177
|
+
const path = getKeyId(id, i);
|
|
178
|
+
try {
|
|
179
|
+
const key = await getKeypair(path);
|
|
180
|
+
throwIfAborted(signal);
|
|
181
|
+
localStorage.setItem(idCounterKey, JSON.stringify(Math.max(idCounter, i + 1)));
|
|
182
|
+
selected.detachAbort();
|
|
183
|
+
return { index: i, path, key, release: selected.release };
|
|
184
|
+
}
|
|
185
|
+
catch (error) {
|
|
186
|
+
selected.release();
|
|
187
|
+
throw error;
|
|
188
|
+
}
|
|
46
189
|
}
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
|
|
190
|
+
throw new Error("Failed to resolve key");
|
|
191
|
+
}
|
|
192
|
+
finally {
|
|
193
|
+
try {
|
|
194
|
+
if (counterLocked) {
|
|
195
|
+
lock.release(idCounterKey);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
finally {
|
|
199
|
+
selectionLock.release();
|
|
200
|
+
}
|
|
201
|
+
}
|
|
57
202
|
};
|
|
58
203
|
export const getAllKeyPairs = async (id = "") => {
|
|
59
204
|
const idCounterKey = ID_COUNTER_KEY + id;
|
package/dist/src/utils.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utils.js","sourceRoot":"","sources":["../../src/utils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AACxD,OAAO,EAAE,cAAc,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AACvE,OAAO,MAAM,MAAM,oBAAoB,CAAC;AACxC,OAAO,EAAE,EAAE,IAAI,IAAI,EAAE,MAAM,MAAM,CAAC;AAClC,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"utils.js","sourceRoot":"","sources":["../../src/utils.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@peerbit/react",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.32",
|
|
4
4
|
"description": "React bindings for Peerbit clients",
|
|
5
5
|
"sideEffects": false,
|
|
6
6
|
"type": "module",
|
|
@@ -53,14 +53,14 @@
|
|
|
53
53
|
"debug": "^4.3.7",
|
|
54
54
|
"detectincognitojs": "^1.6.1",
|
|
55
55
|
"libsodium-wrappers": "0.7.15",
|
|
56
|
-
"uuid": "^
|
|
57
|
-
"@peerbit/canonical-client": "1.1.
|
|
58
|
-
"@peerbit/crypto": "3.1.
|
|
56
|
+
"uuid": "^11.1.1",
|
|
57
|
+
"@peerbit/canonical-client": "1.1.40",
|
|
58
|
+
"@peerbit/crypto": "3.1.4",
|
|
59
|
+
"@peerbit/indexer-interface": "3.0.8",
|
|
60
|
+
"@peerbit/program": "6.0.37",
|
|
59
61
|
"@peerbit/logger": "2.0.1",
|
|
60
|
-
"@peerbit/program": "
|
|
61
|
-
"
|
|
62
|
-
"@peerbit/indexer-interface": "3.0.6",
|
|
63
|
-
"peerbit": "5.3.6"
|
|
62
|
+
"@peerbit/program-react": "0.4.40",
|
|
63
|
+
"peerbit": "5.3.8"
|
|
64
64
|
},
|
|
65
65
|
"devDependencies": {
|
|
66
66
|
"@testing-library/dom": "^10.4.0",
|
|
@@ -69,15 +69,15 @@
|
|
|
69
69
|
"@types/node-localstorage": "^1.3.3",
|
|
70
70
|
"@types/react": "^18.3.11",
|
|
71
71
|
"@types/react-dom": "^18.3.5",
|
|
72
|
-
"@vitest/coverage-v8": "^4.
|
|
72
|
+
"@vitest/coverage-v8": "^4.1.10",
|
|
73
73
|
"chai": "^5.2.0",
|
|
74
74
|
"node-localstorage": "^3.0.5",
|
|
75
75
|
"react-dom": "^19.1.0",
|
|
76
|
-
"sinon": "^
|
|
77
|
-
"vitest": "^4.
|
|
78
|
-
"@peerbit/time": "3.0.
|
|
76
|
+
"sinon": "^22.0.0",
|
|
77
|
+
"vitest": "^4.1.10",
|
|
78
|
+
"@peerbit/time": "3.0.1"
|
|
79
79
|
},
|
|
80
|
-
"author": "
|
|
80
|
+
"author": "Peerbit contributors",
|
|
81
81
|
"license": "MIT",
|
|
82
82
|
"repository": {
|
|
83
83
|
"type": "git",
|
package/src/lockstorage.ts
CHANGED
|
@@ -43,6 +43,32 @@ export class FastMutex {
|
|
|
43
43
|
this.localStorage = localStorage || window.localStorage;
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
+
private clearKeepAlive(key: string) {
|
|
47
|
+
clearInterval(this.intervals.get(key));
|
|
48
|
+
this.intervals.delete(key);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
private getStoredItem(
|
|
52
|
+
key: string,
|
|
53
|
+
): { expiresAt: number; value: string } | undefined {
|
|
54
|
+
const item = this.localStorage.getItem(key);
|
|
55
|
+
if (!item) return;
|
|
56
|
+
const parsed = JSON.parse(item) as { expiresAt: number; value: string };
|
|
57
|
+
// Preserve the legacy grace period so a current bundle does not steal an
|
|
58
|
+
// identity from an older tab whose keep-alive timer was briefly throttled.
|
|
59
|
+
if (Date.now() - parsed.expiresAt >= this.timeout) {
|
|
60
|
+
debug(
|
|
61
|
+
'FastMutex client "%s" removed an expired record on "%s"',
|
|
62
|
+
this.clientId,
|
|
63
|
+
key,
|
|
64
|
+
);
|
|
65
|
+
this.localStorage.removeItem(key);
|
|
66
|
+
this.clearKeepAlive(key);
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
return parsed;
|
|
70
|
+
}
|
|
71
|
+
|
|
46
72
|
lock(
|
|
47
73
|
key: string,
|
|
48
74
|
keepLocked?: () => boolean,
|
|
@@ -89,6 +115,7 @@ export class FastMutex {
|
|
|
89
115
|
if (settled) return;
|
|
90
116
|
settled = true;
|
|
91
117
|
clearPendingRetries();
|
|
118
|
+
this.release(key);
|
|
92
119
|
reject(error);
|
|
93
120
|
};
|
|
94
121
|
|
|
@@ -219,17 +246,44 @@ export class FastMutex {
|
|
|
219
246
|
return this.getItem(x) || this.getItem(y);
|
|
220
247
|
}
|
|
221
248
|
|
|
249
|
+
getLockedOwners(key: string): string[] {
|
|
250
|
+
const owners = [
|
|
251
|
+
this.getItem(this.xPrefix + key),
|
|
252
|
+
this.getItem(this.yPrefix + key),
|
|
253
|
+
].filter((owner): owner is string => !!owner);
|
|
254
|
+
return [...new Set(owners)];
|
|
255
|
+
}
|
|
256
|
+
|
|
222
257
|
release(key: string) {
|
|
258
|
+
this.releaseIfOwnedBy(key, (owner) => owner === this.clientId);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
releaseIfOwnedBy(key: string, predicate: (owner: string) => boolean) {
|
|
223
262
|
debug(
|
|
224
263
|
'FastMutex client "%s" is releasing lock on "%s"',
|
|
225
264
|
this.clientId,
|
|
226
265
|
key,
|
|
227
266
|
);
|
|
228
|
-
|
|
267
|
+
const ps = [this.yPrefix + key, this.xPrefix + key];
|
|
229
268
|
for (const p of ps) {
|
|
230
|
-
|
|
231
|
-
this.
|
|
232
|
-
|
|
269
|
+
this.clearKeepAlive(p);
|
|
270
|
+
const stored = this.getStoredItem(p);
|
|
271
|
+
if (stored && predicate(stored.value)) {
|
|
272
|
+
this.localStorage.removeItem(p);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
pin(key: string) {
|
|
278
|
+
for (const p of [this.xPrefix + key, this.yPrefix + key]) {
|
|
279
|
+
this.clearKeepAlive(p);
|
|
280
|
+
this.localStorage.setItem(
|
|
281
|
+
p,
|
|
282
|
+
JSON.stringify({
|
|
283
|
+
expiresAt: Number.MAX_SAFE_INTEGER,
|
|
284
|
+
value: this.clientId,
|
|
285
|
+
}),
|
|
286
|
+
);
|
|
233
287
|
}
|
|
234
288
|
}
|
|
235
289
|
|
|
@@ -239,11 +293,7 @@ export class FastMutex {
|
|
|
239
293
|
*/
|
|
240
294
|
setItem(key: string, value: any, keepLocked?: () => boolean) {
|
|
241
295
|
// Avoid leaking keep-alive intervals when re-setting the same key (e.g. re-lock with replaceIfSameClient).
|
|
242
|
-
|
|
243
|
-
if (existing) {
|
|
244
|
-
clearInterval(existing);
|
|
245
|
-
this.intervals.delete(key);
|
|
246
|
-
}
|
|
296
|
+
this.clearKeepAlive(key);
|
|
247
297
|
if (!keepLocked) {
|
|
248
298
|
return this.localStorage.setItem(
|
|
249
299
|
key,
|
|
@@ -262,15 +312,13 @@ export class FastMutex {
|
|
|
262
312
|
}),
|
|
263
313
|
);
|
|
264
314
|
const interval = setInterval(() => {
|
|
315
|
+
if (this.getStoredItem(key)?.value !== value) {
|
|
316
|
+
this.clearKeepAlive(key);
|
|
317
|
+
return;
|
|
318
|
+
}
|
|
265
319
|
if (!keepLocked()) {
|
|
266
|
-
this.localStorage.
|
|
267
|
-
|
|
268
|
-
key,
|
|
269
|
-
JSON.stringify({
|
|
270
|
-
expiresAt: 0,
|
|
271
|
-
value,
|
|
272
|
-
}),
|
|
273
|
-
);
|
|
320
|
+
this.localStorage.removeItem(key);
|
|
321
|
+
this.clearKeepAlive(key);
|
|
274
322
|
} else {
|
|
275
323
|
this.localStorage.setItem(
|
|
276
324
|
key,
|
|
@@ -281,6 +329,10 @@ export class FastMutex {
|
|
|
281
329
|
);
|
|
282
330
|
}
|
|
283
331
|
}, this.timeout);
|
|
332
|
+
// A browser timer has no `unref`, while Node returns a Timeout object.
|
|
333
|
+
// Keeping the lease alive must not keep an otherwise finished Node
|
|
334
|
+
// process (or a Vitest coverage worker) alive indefinitely.
|
|
335
|
+
(interval as unknown as { unref?: () => void }).unref?.();
|
|
284
336
|
this.intervals.set(key, interval);
|
|
285
337
|
return ret;
|
|
286
338
|
}
|
|
@@ -290,22 +342,92 @@ export class FastMutex {
|
|
|
290
342
|
* Helper function to parse JSON encoded values set in localStorage
|
|
291
343
|
*/
|
|
292
344
|
getItem(key: string): string | undefined {
|
|
293
|
-
|
|
294
|
-
|
|
345
|
+
return this.getStoredItem(key)?.value;
|
|
346
|
+
}
|
|
347
|
+
}
|
|
295
348
|
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
349
|
+
export type WebLockLease = {
|
|
350
|
+
release: () => void;
|
|
351
|
+
detachAbort: () => void;
|
|
352
|
+
};
|
|
353
|
+
|
|
354
|
+
const getAbortReason = (signal: AbortSignal) =>
|
|
355
|
+
signal.reason ?? new DOMException("Aborted", "AbortError");
|
|
356
|
+
|
|
357
|
+
export const acquireWebLock = (
|
|
358
|
+
manager: LockManager,
|
|
359
|
+
name: string,
|
|
360
|
+
options: { ifAvailable?: boolean; signal?: AbortSignal } = {},
|
|
361
|
+
): Promise<WebLockLease | undefined> => {
|
|
362
|
+
return new Promise((resolve, reject) => {
|
|
363
|
+
let settled = false;
|
|
364
|
+
let held = false;
|
|
365
|
+
let released = false;
|
|
366
|
+
let releaseHold: (() => void) | undefined;
|
|
367
|
+
const hold = new Promise<void>((resolveHold) => {
|
|
368
|
+
releaseHold = resolveHold;
|
|
369
|
+
});
|
|
370
|
+
const signal = options.signal;
|
|
371
|
+
|
|
372
|
+
const detachAbort = () => {
|
|
373
|
+
signal?.removeEventListener("abort", onAbort);
|
|
374
|
+
};
|
|
375
|
+
const release = () => {
|
|
376
|
+
if (released) return;
|
|
377
|
+
released = true;
|
|
378
|
+
detachAbort();
|
|
379
|
+
releaseHold?.();
|
|
380
|
+
};
|
|
381
|
+
const rejectOnce = (error: unknown) => {
|
|
382
|
+
if (settled) return;
|
|
383
|
+
settled = true;
|
|
384
|
+
detachAbort();
|
|
385
|
+
reject(error);
|
|
386
|
+
};
|
|
387
|
+
const onAbort = () => {
|
|
388
|
+
if (held) {
|
|
389
|
+
release();
|
|
390
|
+
}
|
|
391
|
+
rejectOnce(getAbortReason(signal!));
|
|
392
|
+
};
|
|
393
|
+
|
|
394
|
+
if (signal?.aborted) {
|
|
395
|
+
rejectOnce(getAbortReason(signal));
|
|
306
396
|
return;
|
|
307
397
|
}
|
|
398
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
308
399
|
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
400
|
+
void manager
|
|
401
|
+
.request(
|
|
402
|
+
name,
|
|
403
|
+
options.ifAvailable
|
|
404
|
+
? { mode: "exclusive", ifAvailable: true }
|
|
405
|
+
: signal
|
|
406
|
+
? { mode: "exclusive", signal }
|
|
407
|
+
: { mode: "exclusive" },
|
|
408
|
+
async (lock) => {
|
|
409
|
+
if (!lock) {
|
|
410
|
+
if (!settled) {
|
|
411
|
+
settled = true;
|
|
412
|
+
detachAbort();
|
|
413
|
+
resolve(undefined);
|
|
414
|
+
}
|
|
415
|
+
return;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
held = true;
|
|
419
|
+
if (signal?.aborted) {
|
|
420
|
+
release();
|
|
421
|
+
rejectOnce(getAbortReason(signal));
|
|
422
|
+
return;
|
|
423
|
+
}
|
|
424
|
+
if (!settled) {
|
|
425
|
+
settled = true;
|
|
426
|
+
resolve({ release, detachAbort });
|
|
427
|
+
}
|
|
428
|
+
await hold;
|
|
429
|
+
},
|
|
430
|
+
)
|
|
431
|
+
.catch((error) => rejectOnce(error));
|
|
432
|
+
});
|
|
433
|
+
};
|