@universal-lock/web-locks 1.0.0
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/LICENSE +21 -0
- package/README.md +86 -0
- package/dist/index.d.mts +5 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.global.js +70 -0
- package/dist/index.global.js.map +1 -0
- package/dist/index.js +72 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +47 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +51 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Lucas Rainett
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# @universal-lock/web-locks
|
|
2
|
+
|
|
3
|
+
Web Locks API backend for [`universal-lock`](https://github.com/lucasrainett/universal-lock). Provides cross-tab locking using the native browser [Web Locks API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Locks_API).
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install universal-lock @universal-lock/web-locks
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
### ESM
|
|
14
|
+
|
|
15
|
+
```typescript
|
|
16
|
+
import { lockFactory } from "universal-lock";
|
|
17
|
+
import { createBackend } from "@universal-lock/web-locks";
|
|
18
|
+
|
|
19
|
+
const lock = lockFactory(createBackend());
|
|
20
|
+
|
|
21
|
+
const release = await lock.acquire("my-resource");
|
|
22
|
+
try {
|
|
23
|
+
// critical section — safe across browser tabs
|
|
24
|
+
} finally {
|
|
25
|
+
await release();
|
|
26
|
+
}
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
### CommonJS
|
|
30
|
+
|
|
31
|
+
```javascript
|
|
32
|
+
const { lockFactory } = require("universal-lock");
|
|
33
|
+
const { createBackend } = require("@universal-lock/web-locks");
|
|
34
|
+
|
|
35
|
+
const lock = lockFactory(createBackend());
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### Browser (IIFE)
|
|
39
|
+
|
|
40
|
+
```html
|
|
41
|
+
<script src="https://unpkg.com/@universal-lock/web-locks/dist/index.global.js"></script>
|
|
42
|
+
<script src="https://unpkg.com/universal-lock/dist/index.global.js"></script>
|
|
43
|
+
<script>
|
|
44
|
+
const lock = UniversalLock.lockFactory(UniversalLockWebLocks.createBackend());
|
|
45
|
+
</script>
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## API
|
|
49
|
+
|
|
50
|
+
### `createBackend()`
|
|
51
|
+
|
|
52
|
+
Creates a Web Locks API backend instance. No arguments required.
|
|
53
|
+
|
|
54
|
+
```typescript
|
|
55
|
+
import { createBackend } from "@universal-lock/web-locks";
|
|
56
|
+
|
|
57
|
+
const backend = createBackend();
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## How It Works
|
|
61
|
+
|
|
62
|
+
Uses `navigator.locks.request()` with `ifAvailable: true` mode. Locks are managed natively by the browser — no external storage is needed. Lock references and release callbacks are stored in memory.
|
|
63
|
+
|
|
64
|
+
## Advantages
|
|
65
|
+
|
|
66
|
+
- **Automatic cleanup** — locks are released if a tab crashes or navigates away
|
|
67
|
+
- **True atomicity** — OS-level lock guarantees, no race conditions
|
|
68
|
+
- **No storage needed** — no localStorage or cookies involved
|
|
69
|
+
|
|
70
|
+
## When to Use
|
|
71
|
+
|
|
72
|
+
- Cross-tab locking in modern browsers (recommended over localStorage backend)
|
|
73
|
+
- Applications that need reliable lock cleanup on tab crash
|
|
74
|
+
|
|
75
|
+
## Limitations
|
|
76
|
+
|
|
77
|
+
- Throws during `setup` if the Web Locks API is unavailable. For older browsers, use [`@universal-lock/local-storage`](https://www.npmjs.com/package/@universal-lock/local-storage) as a fallback.
|
|
78
|
+
- Browser-only — not available in Node.js.
|
|
79
|
+
|
|
80
|
+
## Browser Support
|
|
81
|
+
|
|
82
|
+
The Web Locks API is supported in Chrome 69+, Firefox 96+, Safari 15.4+, and Edge 79+. See [Can I use](https://caniuse.com/web-locks) for details.
|
|
83
|
+
|
|
84
|
+
## License
|
|
85
|
+
|
|
86
|
+
[MIT](https://github.com/lucasrainett/universal-lock/blob/master/LICENSE)
|
package/dist/index.d.mts
ADDED
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var UniversalLockWebLocks = (() => {
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
+
var __export = (target, all) => {
|
|
8
|
+
for (var name in all)
|
|
9
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
10
|
+
};
|
|
11
|
+
var __copyProps = (to, from, except, desc) => {
|
|
12
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
13
|
+
for (let key of __getOwnPropNames(from))
|
|
14
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
15
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
16
|
+
}
|
|
17
|
+
return to;
|
|
18
|
+
};
|
|
19
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
20
|
+
|
|
21
|
+
// src/index.ts
|
|
22
|
+
var index_exports = {};
|
|
23
|
+
__export(index_exports, {
|
|
24
|
+
createBackend: () => createBackend
|
|
25
|
+
});
|
|
26
|
+
var createBackend = () => {
|
|
27
|
+
const locks = {};
|
|
28
|
+
const setup = async () => {
|
|
29
|
+
if (typeof navigator === "undefined" || !navigator.locks) {
|
|
30
|
+
throw new Error("Web Locks API is not available");
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
const acquire = async (lockName, _stale, lockId) => {
|
|
34
|
+
await new Promise((resolve, reject) => {
|
|
35
|
+
navigator.locks.request(lockName, { ifAvailable: true }, (lock) => {
|
|
36
|
+
if (!lock) {
|
|
37
|
+
reject(new Error(`${lockName} already locked`));
|
|
38
|
+
return Promise.resolve();
|
|
39
|
+
}
|
|
40
|
+
return new Promise((releaseResolve) => {
|
|
41
|
+
locks[lockName] = { lockId, release: releaseResolve };
|
|
42
|
+
resolve();
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
};
|
|
47
|
+
const renew = async (lockName, lockId) => {
|
|
48
|
+
const existing = locks[lockName];
|
|
49
|
+
if (!existing) throw new Error(`${lockName} not locked`);
|
|
50
|
+
if (existing.lockId !== lockId)
|
|
51
|
+
throw new Error(`${lockName} not owned by caller`);
|
|
52
|
+
};
|
|
53
|
+
const release = async (lockName, lockId) => {
|
|
54
|
+
const existing = locks[lockName];
|
|
55
|
+
if (!existing) throw new Error(`${lockName} not locked`);
|
|
56
|
+
if (existing.lockId !== lockId)
|
|
57
|
+
throw new Error(`${lockName} not owned by caller`);
|
|
58
|
+
existing.release();
|
|
59
|
+
delete locks[lockName];
|
|
60
|
+
};
|
|
61
|
+
return {
|
|
62
|
+
setup,
|
|
63
|
+
acquire,
|
|
64
|
+
renew,
|
|
65
|
+
release
|
|
66
|
+
};
|
|
67
|
+
};
|
|
68
|
+
return __toCommonJS(index_exports);
|
|
69
|
+
})();
|
|
70
|
+
//# sourceMappingURL=index.global.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["import type {\n\tBackend,\n\tBackendAcquireFunction,\n\tBackendFactory,\n\tBackendReleaseFunction,\n\tBackendRenewFunction,\n\tBackendSetupFunction,\n\tCallbackLockEntry,\n} from \"@universal-lock/types\";\n\nexport const createBackend: BackendFactory = (): Backend => {\n\tconst locks: Record<string, CallbackLockEntry> = {};\n\n\tconst setup: BackendSetupFunction = async () => {\n\t\tif (typeof navigator === \"undefined\" || !navigator.locks) {\n\t\t\tthrow new Error(\"Web Locks API is not available\");\n\t\t}\n\t};\n\n\t// The Web Locks API holds a lock for the duration of the callback's returned promise.\n\t// To control when the lock is released, we return a promise whose resolve function\n\t// (releaseResolve) is stored and called later in the release() method.\n\t// ifAvailable: true makes this non-blocking — fails immediately if the lock is held.\n\tconst acquire: BackendAcquireFunction = async (\n\t\tlockName,\n\t\t_stale,\n\t\tlockId,\n\t) => {\n\t\tawait new Promise<void>((resolve, reject) => {\n\t\t\tnavigator.locks.request(lockName, { ifAvailable: true }, (lock) => {\n\t\t\t\tif (!lock) {\n\t\t\t\t\treject(new Error(`${lockName} already locked`));\n\t\t\t\t\t// Must return a resolved promise to complete the lock request callback\n\t\t\t\t\treturn Promise.resolve();\n\t\t\t\t}\n\n\t\t\t\t// Return an unresolved promise — the browser holds the lock until this resolves\n\t\t\t\treturn new Promise<void>((releaseResolve) => {\n\t\t\t\t\tlocks[lockName] = { lockId, release: releaseResolve };\n\t\t\t\t\tresolve();\n\t\t\t\t});\n\t\t\t});\n\t\t});\n\t};\n\n\t// Renew is a no-op for Web Locks — the browser holds the lock until the promise resolves,\n\t// so there is no TTL to refresh. We only validate ownership here.\n\tconst renew: BackendRenewFunction = async (lockName, lockId) => {\n\t\tconst existing = locks[lockName];\n\t\tif (!existing) throw new Error(`${lockName} not locked`);\n\t\tif (existing.lockId !== lockId)\n\t\t\tthrow new Error(`${lockName} not owned by caller`);\n\t};\n\n\tconst release: BackendReleaseFunction = async (lockName, lockId) => {\n\t\tconst existing = locks[lockName];\n\t\tif (!existing) throw new Error(`${lockName} not locked`);\n\t\tif (existing.lockId !== lockId)\n\t\t\tthrow new Error(`${lockName} not owned by caller`);\n\t\texisting.release();\n\t\tdelete locks[lockName];\n\t};\n\n\treturn {\n\t\tsetup,\n\t\tacquire,\n\t\trenew,\n\t\trelease,\n\t};\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAUO,MAAM,gBAAgC,MAAe;AAC3D,UAAM,QAA2C,CAAC;AAElD,UAAM,QAA8B,YAAY;AAC/C,UAAI,OAAO,cAAc,eAAe,CAAC,UAAU,OAAO;AACzD,cAAM,IAAI,MAAM,gCAAgC;AAAA,MACjD;AAAA,IACD;AAMA,UAAM,UAAkC,OACvC,UACA,QACA,WACI;AACJ,YAAM,IAAI,QAAc,CAAC,SAAS,WAAW;AAC5C,kBAAU,MAAM,QAAQ,UAAU,EAAE,aAAa,KAAK,GAAG,CAAC,SAAS;AAClE,cAAI,CAAC,MAAM;AACV,mBAAO,IAAI,MAAM,GAAG,QAAQ,iBAAiB,CAAC;AAE9C,mBAAO,QAAQ,QAAQ;AAAA,UACxB;AAGA,iBAAO,IAAI,QAAc,CAAC,mBAAmB;AAC5C,kBAAM,QAAQ,IAAI,EAAE,QAAQ,SAAS,eAAe;AACpD,oBAAQ;AAAA,UACT,CAAC;AAAA,QACF,CAAC;AAAA,MACF,CAAC;AAAA,IACF;AAIA,UAAM,QAA8B,OAAO,UAAU,WAAW;AAC/D,YAAM,WAAW,MAAM,QAAQ;AAC/B,UAAI,CAAC,SAAU,OAAM,IAAI,MAAM,GAAG,QAAQ,aAAa;AACvD,UAAI,SAAS,WAAW;AACvB,cAAM,IAAI,MAAM,GAAG,QAAQ,sBAAsB;AAAA,IACnD;AAEA,UAAM,UAAkC,OAAO,UAAU,WAAW;AACnE,YAAM,WAAW,MAAM,QAAQ;AAC/B,UAAI,CAAC,SAAU,OAAM,IAAI,MAAM,GAAG,QAAQ,aAAa;AACvD,UAAI,SAAS,WAAW;AACvB,cAAM,IAAI,MAAM,GAAG,QAAQ,sBAAsB;AAClD,eAAS,QAAQ;AACjB,aAAO,MAAM,QAAQ;AAAA,IACtB;AAEA,WAAO;AAAA,MACN;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACD;AAAA,EACD;","names":[]}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
createBackend: () => createBackend
|
|
24
|
+
});
|
|
25
|
+
module.exports = __toCommonJS(index_exports);
|
|
26
|
+
var createBackend = () => {
|
|
27
|
+
const locks = {};
|
|
28
|
+
const setup = async () => {
|
|
29
|
+
if (typeof navigator === "undefined" || !navigator.locks) {
|
|
30
|
+
throw new Error("Web Locks API is not available");
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
const acquire = async (lockName, _stale, lockId) => {
|
|
34
|
+
await new Promise((resolve, reject) => {
|
|
35
|
+
navigator.locks.request(lockName, { ifAvailable: true }, (lock) => {
|
|
36
|
+
if (!lock) {
|
|
37
|
+
reject(new Error(`${lockName} already locked`));
|
|
38
|
+
return Promise.resolve();
|
|
39
|
+
}
|
|
40
|
+
return new Promise((releaseResolve) => {
|
|
41
|
+
locks[lockName] = { lockId, release: releaseResolve };
|
|
42
|
+
resolve();
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
};
|
|
47
|
+
const renew = async (lockName, lockId) => {
|
|
48
|
+
const existing = locks[lockName];
|
|
49
|
+
if (!existing) throw new Error(`${lockName} not locked`);
|
|
50
|
+
if (existing.lockId !== lockId)
|
|
51
|
+
throw new Error(`${lockName} not owned by caller`);
|
|
52
|
+
};
|
|
53
|
+
const release = async (lockName, lockId) => {
|
|
54
|
+
const existing = locks[lockName];
|
|
55
|
+
if (!existing) throw new Error(`${lockName} not locked`);
|
|
56
|
+
if (existing.lockId !== lockId)
|
|
57
|
+
throw new Error(`${lockName} not owned by caller`);
|
|
58
|
+
existing.release();
|
|
59
|
+
delete locks[lockName];
|
|
60
|
+
};
|
|
61
|
+
return {
|
|
62
|
+
setup,
|
|
63
|
+
acquire,
|
|
64
|
+
renew,
|
|
65
|
+
release
|
|
66
|
+
};
|
|
67
|
+
};
|
|
68
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
69
|
+
0 && (module.exports = {
|
|
70
|
+
createBackend
|
|
71
|
+
});
|
|
72
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["import type {\n\tBackend,\n\tBackendAcquireFunction,\n\tBackendFactory,\n\tBackendReleaseFunction,\n\tBackendRenewFunction,\n\tBackendSetupFunction,\n\tCallbackLockEntry,\n} from \"@universal-lock/types\";\n\nexport const createBackend: BackendFactory = (): Backend => {\n\tconst locks: Record<string, CallbackLockEntry> = {};\n\n\tconst setup: BackendSetupFunction = async () => {\n\t\tif (typeof navigator === \"undefined\" || !navigator.locks) {\n\t\t\tthrow new Error(\"Web Locks API is not available\");\n\t\t}\n\t};\n\n\t// The Web Locks API holds a lock for the duration of the callback's returned promise.\n\t// To control when the lock is released, we return a promise whose resolve function\n\t// (releaseResolve) is stored and called later in the release() method.\n\t// ifAvailable: true makes this non-blocking — fails immediately if the lock is held.\n\tconst acquire: BackendAcquireFunction = async (\n\t\tlockName,\n\t\t_stale,\n\t\tlockId,\n\t) => {\n\t\tawait new Promise<void>((resolve, reject) => {\n\t\t\tnavigator.locks.request(lockName, { ifAvailable: true }, (lock) => {\n\t\t\t\tif (!lock) {\n\t\t\t\t\treject(new Error(`${lockName} already locked`));\n\t\t\t\t\t// Must return a resolved promise to complete the lock request callback\n\t\t\t\t\treturn Promise.resolve();\n\t\t\t\t}\n\n\t\t\t\t// Return an unresolved promise — the browser holds the lock until this resolves\n\t\t\t\treturn new Promise<void>((releaseResolve) => {\n\t\t\t\t\tlocks[lockName] = { lockId, release: releaseResolve };\n\t\t\t\t\tresolve();\n\t\t\t\t});\n\t\t\t});\n\t\t});\n\t};\n\n\t// Renew is a no-op for Web Locks — the browser holds the lock until the promise resolves,\n\t// so there is no TTL to refresh. We only validate ownership here.\n\tconst renew: BackendRenewFunction = async (lockName, lockId) => {\n\t\tconst existing = locks[lockName];\n\t\tif (!existing) throw new Error(`${lockName} not locked`);\n\t\tif (existing.lockId !== lockId)\n\t\t\tthrow new Error(`${lockName} not owned by caller`);\n\t};\n\n\tconst release: BackendReleaseFunction = async (lockName, lockId) => {\n\t\tconst existing = locks[lockName];\n\t\tif (!existing) throw new Error(`${lockName} not locked`);\n\t\tif (existing.lockId !== lockId)\n\t\t\tthrow new Error(`${lockName} not owned by caller`);\n\t\texisting.release();\n\t\tdelete locks[lockName];\n\t};\n\n\treturn {\n\t\tsetup,\n\t\tacquire,\n\t\trenew,\n\t\trelease,\n\t};\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAUO,IAAM,gBAAgC,MAAe;AAC3D,QAAM,QAA2C,CAAC;AAElD,QAAM,QAA8B,YAAY;AAC/C,QAAI,OAAO,cAAc,eAAe,CAAC,UAAU,OAAO;AACzD,YAAM,IAAI,MAAM,gCAAgC;AAAA,IACjD;AAAA,EACD;AAMA,QAAM,UAAkC,OACvC,UACA,QACA,WACI;AACJ,UAAM,IAAI,QAAc,CAAC,SAAS,WAAW;AAC5C,gBAAU,MAAM,QAAQ,UAAU,EAAE,aAAa,KAAK,GAAG,CAAC,SAAS;AAClE,YAAI,CAAC,MAAM;AACV,iBAAO,IAAI,MAAM,GAAG,QAAQ,iBAAiB,CAAC;AAE9C,iBAAO,QAAQ,QAAQ;AAAA,QACxB;AAGA,eAAO,IAAI,QAAc,CAAC,mBAAmB;AAC5C,gBAAM,QAAQ,IAAI,EAAE,QAAQ,SAAS,eAAe;AACpD,kBAAQ;AAAA,QACT,CAAC;AAAA,MACF,CAAC;AAAA,IACF,CAAC;AAAA,EACF;AAIA,QAAM,QAA8B,OAAO,UAAU,WAAW;AAC/D,UAAM,WAAW,MAAM,QAAQ;AAC/B,QAAI,CAAC,SAAU,OAAM,IAAI,MAAM,GAAG,QAAQ,aAAa;AACvD,QAAI,SAAS,WAAW;AACvB,YAAM,IAAI,MAAM,GAAG,QAAQ,sBAAsB;AAAA,EACnD;AAEA,QAAM,UAAkC,OAAO,UAAU,WAAW;AACnE,UAAM,WAAW,MAAM,QAAQ;AAC/B,QAAI,CAAC,SAAU,OAAM,IAAI,MAAM,GAAG,QAAQ,aAAa;AACvD,QAAI,SAAS,WAAW;AACvB,YAAM,IAAI,MAAM,GAAG,QAAQ,sBAAsB;AAClD,aAAS,QAAQ;AACjB,WAAO,MAAM,QAAQ;AAAA,EACtB;AAEA,SAAO;AAAA,IACN;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACD;AACD;","names":[]}
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
// src/index.ts
|
|
2
|
+
var createBackend = () => {
|
|
3
|
+
const locks = {};
|
|
4
|
+
const setup = async () => {
|
|
5
|
+
if (typeof navigator === "undefined" || !navigator.locks) {
|
|
6
|
+
throw new Error("Web Locks API is not available");
|
|
7
|
+
}
|
|
8
|
+
};
|
|
9
|
+
const acquire = async (lockName, _stale, lockId) => {
|
|
10
|
+
await new Promise((resolve, reject) => {
|
|
11
|
+
navigator.locks.request(lockName, { ifAvailable: true }, (lock) => {
|
|
12
|
+
if (!lock) {
|
|
13
|
+
reject(new Error(`${lockName} already locked`));
|
|
14
|
+
return Promise.resolve();
|
|
15
|
+
}
|
|
16
|
+
return new Promise((releaseResolve) => {
|
|
17
|
+
locks[lockName] = { lockId, release: releaseResolve };
|
|
18
|
+
resolve();
|
|
19
|
+
});
|
|
20
|
+
});
|
|
21
|
+
});
|
|
22
|
+
};
|
|
23
|
+
const renew = async (lockName, lockId) => {
|
|
24
|
+
const existing = locks[lockName];
|
|
25
|
+
if (!existing) throw new Error(`${lockName} not locked`);
|
|
26
|
+
if (existing.lockId !== lockId)
|
|
27
|
+
throw new Error(`${lockName} not owned by caller`);
|
|
28
|
+
};
|
|
29
|
+
const release = async (lockName, lockId) => {
|
|
30
|
+
const existing = locks[lockName];
|
|
31
|
+
if (!existing) throw new Error(`${lockName} not locked`);
|
|
32
|
+
if (existing.lockId !== lockId)
|
|
33
|
+
throw new Error(`${lockName} not owned by caller`);
|
|
34
|
+
existing.release();
|
|
35
|
+
delete locks[lockName];
|
|
36
|
+
};
|
|
37
|
+
return {
|
|
38
|
+
setup,
|
|
39
|
+
acquire,
|
|
40
|
+
renew,
|
|
41
|
+
release
|
|
42
|
+
};
|
|
43
|
+
};
|
|
44
|
+
export {
|
|
45
|
+
createBackend
|
|
46
|
+
};
|
|
47
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["import type {\n\tBackend,\n\tBackendAcquireFunction,\n\tBackendFactory,\n\tBackendReleaseFunction,\n\tBackendRenewFunction,\n\tBackendSetupFunction,\n\tCallbackLockEntry,\n} from \"@universal-lock/types\";\n\nexport const createBackend: BackendFactory = (): Backend => {\n\tconst locks: Record<string, CallbackLockEntry> = {};\n\n\tconst setup: BackendSetupFunction = async () => {\n\t\tif (typeof navigator === \"undefined\" || !navigator.locks) {\n\t\t\tthrow new Error(\"Web Locks API is not available\");\n\t\t}\n\t};\n\n\t// The Web Locks API holds a lock for the duration of the callback's returned promise.\n\t// To control when the lock is released, we return a promise whose resolve function\n\t// (releaseResolve) is stored and called later in the release() method.\n\t// ifAvailable: true makes this non-blocking — fails immediately if the lock is held.\n\tconst acquire: BackendAcquireFunction = async (\n\t\tlockName,\n\t\t_stale,\n\t\tlockId,\n\t) => {\n\t\tawait new Promise<void>((resolve, reject) => {\n\t\t\tnavigator.locks.request(lockName, { ifAvailable: true }, (lock) => {\n\t\t\t\tif (!lock) {\n\t\t\t\t\treject(new Error(`${lockName} already locked`));\n\t\t\t\t\t// Must return a resolved promise to complete the lock request callback\n\t\t\t\t\treturn Promise.resolve();\n\t\t\t\t}\n\n\t\t\t\t// Return an unresolved promise — the browser holds the lock until this resolves\n\t\t\t\treturn new Promise<void>((releaseResolve) => {\n\t\t\t\t\tlocks[lockName] = { lockId, release: releaseResolve };\n\t\t\t\t\tresolve();\n\t\t\t\t});\n\t\t\t});\n\t\t});\n\t};\n\n\t// Renew is a no-op for Web Locks — the browser holds the lock until the promise resolves,\n\t// so there is no TTL to refresh. We only validate ownership here.\n\tconst renew: BackendRenewFunction = async (lockName, lockId) => {\n\t\tconst existing = locks[lockName];\n\t\tif (!existing) throw new Error(`${lockName} not locked`);\n\t\tif (existing.lockId !== lockId)\n\t\t\tthrow new Error(`${lockName} not owned by caller`);\n\t};\n\n\tconst release: BackendReleaseFunction = async (lockName, lockId) => {\n\t\tconst existing = locks[lockName];\n\t\tif (!existing) throw new Error(`${lockName} not locked`);\n\t\tif (existing.lockId !== lockId)\n\t\t\tthrow new Error(`${lockName} not owned by caller`);\n\t\texisting.release();\n\t\tdelete locks[lockName];\n\t};\n\n\treturn {\n\t\tsetup,\n\t\tacquire,\n\t\trenew,\n\t\trelease,\n\t};\n};\n"],"mappings":";AAUO,IAAM,gBAAgC,MAAe;AAC3D,QAAM,QAA2C,CAAC;AAElD,QAAM,QAA8B,YAAY;AAC/C,QAAI,OAAO,cAAc,eAAe,CAAC,UAAU,OAAO;AACzD,YAAM,IAAI,MAAM,gCAAgC;AAAA,IACjD;AAAA,EACD;AAMA,QAAM,UAAkC,OACvC,UACA,QACA,WACI;AACJ,UAAM,IAAI,QAAc,CAAC,SAAS,WAAW;AAC5C,gBAAU,MAAM,QAAQ,UAAU,EAAE,aAAa,KAAK,GAAG,CAAC,SAAS;AAClE,YAAI,CAAC,MAAM;AACV,iBAAO,IAAI,MAAM,GAAG,QAAQ,iBAAiB,CAAC;AAE9C,iBAAO,QAAQ,QAAQ;AAAA,QACxB;AAGA,eAAO,IAAI,QAAc,CAAC,mBAAmB;AAC5C,gBAAM,QAAQ,IAAI,EAAE,QAAQ,SAAS,eAAe;AACpD,kBAAQ;AAAA,QACT,CAAC;AAAA,MACF,CAAC;AAAA,IACF,CAAC;AAAA,EACF;AAIA,QAAM,QAA8B,OAAO,UAAU,WAAW;AAC/D,UAAM,WAAW,MAAM,QAAQ;AAC/B,QAAI,CAAC,SAAU,OAAM,IAAI,MAAM,GAAG,QAAQ,aAAa;AACvD,QAAI,SAAS,WAAW;AACvB,YAAM,IAAI,MAAM,GAAG,QAAQ,sBAAsB;AAAA,EACnD;AAEA,QAAM,UAAkC,OAAO,UAAU,WAAW;AACnE,UAAM,WAAW,MAAM,QAAQ;AAC/B,QAAI,CAAC,SAAU,OAAM,IAAI,MAAM,GAAG,QAAQ,aAAa;AACvD,QAAI,SAAS,WAAW;AACvB,YAAM,IAAI,MAAM,GAAG,QAAQ,sBAAsB;AAClD,aAAS,QAAQ;AACjB,WAAO,MAAM,QAAQ;AAAA,EACtB;AAEA,SAAO;AAAA,IACN;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACD;AACD;","names":[]}
|
package/package.json
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@universal-lock/web-locks",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Web Locks API backend for universal-lock",
|
|
5
|
+
"sideEffects": false,
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"module": "dist/index.mjs",
|
|
8
|
+
"browser": "dist/index.global.js",
|
|
9
|
+
"types": "dist/index.d.ts",
|
|
10
|
+
"exports": {
|
|
11
|
+
".": {
|
|
12
|
+
"import": {
|
|
13
|
+
"types": "./dist/index.d.mts",
|
|
14
|
+
"default": "./dist/index.mjs"
|
|
15
|
+
},
|
|
16
|
+
"require": {
|
|
17
|
+
"types": "./dist/index.d.ts",
|
|
18
|
+
"default": "./dist/index.js"
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
"files": [
|
|
23
|
+
"/dist"
|
|
24
|
+
],
|
|
25
|
+
"repository": {
|
|
26
|
+
"type": "git",
|
|
27
|
+
"url": "https://github.com/lucasrainett/universal-lock",
|
|
28
|
+
"directory": "packages/web-locks"
|
|
29
|
+
},
|
|
30
|
+
"keywords": [
|
|
31
|
+
"universal-lock",
|
|
32
|
+
"lock",
|
|
33
|
+
"mutex",
|
|
34
|
+
"web-locks",
|
|
35
|
+
"browser",
|
|
36
|
+
"backend"
|
|
37
|
+
],
|
|
38
|
+
"author": {
|
|
39
|
+
"name": "Lucas Rainett",
|
|
40
|
+
"email": "lucas@rainett.dev",
|
|
41
|
+
"url": "https://github.com/lucasrainett"
|
|
42
|
+
},
|
|
43
|
+
"license": "MIT",
|
|
44
|
+
"dependencies": {
|
|
45
|
+
"@universal-lock/types": "1.0.0"
|
|
46
|
+
},
|
|
47
|
+
"scripts": {
|
|
48
|
+
"build": "tsup",
|
|
49
|
+
"clean": "rm -rf dist"
|
|
50
|
+
}
|
|
51
|
+
}
|