webext-storage 2.0.1 → 3.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/distribution/storage-item-map.d.ts +6 -5
- package/distribution/storage-item-map.js +26 -19
- package/distribution/storage-item.d.ts +6 -7
- package/distribution/storage-item.js +24 -20
- package/distribution/storage-item.test-d.js +2 -1
- package/distribution/utils.d.ts +2 -0
- package/distribution/utils.js +12 -0
- package/package.json +7 -20
- package/readme.md +1 -1
|
@@ -7,16 +7,17 @@ export declare class StorageItemMap<
|
|
|
7
7
|
Base,
|
|
8
8
|
/** The return type will be undefined unless you provide a default value */
|
|
9
9
|
Return = Base | undefined> {
|
|
10
|
+
#private;
|
|
10
11
|
readonly prefix: `${string}:::`;
|
|
11
12
|
readonly areaName: chrome.storage.AreaName;
|
|
12
13
|
readonly defaultValue?: Return;
|
|
13
14
|
constructor(key: string, { area, defaultValue, }?: StorageItemMapOptions<Exclude<Return, undefined>>);
|
|
14
|
-
has
|
|
15
|
-
get
|
|
16
|
-
set
|
|
17
|
-
remove
|
|
15
|
+
has(secondaryKey: string): Promise<boolean>;
|
|
16
|
+
get(secondaryKey: string): Promise<Return>;
|
|
17
|
+
set(secondaryKey: string, value: Exclude<Return, undefined>): Promise<void>;
|
|
18
|
+
remove(secondaryKey: string): Promise<void>;
|
|
18
19
|
/** @deprecated Only here to match the Map API; use `remove` instead */
|
|
19
|
-
delete
|
|
20
|
+
delete(secondaryKey: string): Promise<void>;
|
|
20
21
|
onChanged(callback: (key: string, value: Exclude<Return, undefined>) => void, signal?: AbortSignal): void;
|
|
21
22
|
private getRawStorageKey;
|
|
22
23
|
private getSecondaryStorageKey;
|
|
@@ -1,55 +1,62 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { assertChromeStorageAvailable, hasStorageValueChanged } from './utils.js';
|
|
2
2
|
export class StorageItemMap {
|
|
3
3
|
prefix;
|
|
4
4
|
areaName;
|
|
5
5
|
defaultValue;
|
|
6
|
+
get #storage() {
|
|
7
|
+
assertChromeStorageAvailable();
|
|
8
|
+
return chrome.storage[this.areaName];
|
|
9
|
+
}
|
|
6
10
|
constructor(key, { area = 'local', defaultValue, } = {}) {
|
|
7
11
|
this.prefix = `${key}:::`;
|
|
8
12
|
this.areaName = area;
|
|
9
13
|
this.defaultValue = defaultValue;
|
|
10
14
|
}
|
|
11
|
-
|
|
15
|
+
async has(secondaryKey) {
|
|
12
16
|
const rawStorageKey = this.getRawStorageKey(secondaryKey);
|
|
13
|
-
const result = await
|
|
17
|
+
const result = await this.#storage.get(rawStorageKey);
|
|
14
18
|
// Do not use Object.hasOwn() due to https://github.com/RickyMarou/jest-webextension-mock/issues/20
|
|
15
19
|
return result[rawStorageKey] !== undefined;
|
|
16
|
-
}
|
|
17
|
-
|
|
20
|
+
}
|
|
21
|
+
async get(secondaryKey) {
|
|
18
22
|
const rawStorageKey = this.getRawStorageKey(secondaryKey);
|
|
19
|
-
const result = await
|
|
23
|
+
const result = await this.#storage.get(rawStorageKey);
|
|
20
24
|
// Do not use Object.hasOwn() due to https://github.com/RickyMarou/jest-webextension-mock/issues/20
|
|
21
25
|
if (result[rawStorageKey] === undefined) {
|
|
26
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- Assumes the user never uses the Storage API directly for this key
|
|
22
27
|
return this.defaultValue;
|
|
23
28
|
}
|
|
24
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-return -- Assumes the user never uses the Storage API directly for this key
|
|
29
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-type-assertion -- Assumes the user never uses the Storage API directly for this key
|
|
25
30
|
return result[rawStorageKey];
|
|
26
|
-
}
|
|
27
|
-
|
|
31
|
+
}
|
|
32
|
+
async set(secondaryKey, value) {
|
|
28
33
|
const rawStorageKey = this.getRawStorageKey(secondaryKey);
|
|
29
34
|
// eslint-disable-next-line unicorn/prefer-ternary -- ur rong
|
|
30
35
|
if (value === undefined) {
|
|
31
|
-
await
|
|
36
|
+
await this.#storage.remove(rawStorageKey);
|
|
32
37
|
}
|
|
33
38
|
else {
|
|
34
|
-
await
|
|
39
|
+
await this.#storage.set({ [rawStorageKey]: value });
|
|
35
40
|
}
|
|
36
|
-
}
|
|
37
|
-
|
|
41
|
+
}
|
|
42
|
+
async remove(secondaryKey) {
|
|
38
43
|
const rawStorageKey = this.getRawStorageKey(secondaryKey);
|
|
39
|
-
await
|
|
40
|
-
}
|
|
44
|
+
await this.#storage.remove(rawStorageKey);
|
|
45
|
+
}
|
|
41
46
|
/** @deprecated Only here to match the Map API; use `remove` instead */
|
|
42
|
-
|
|
43
|
-
|
|
47
|
+
async delete(secondaryKey) {
|
|
48
|
+
return this.remove(secondaryKey);
|
|
49
|
+
}
|
|
44
50
|
onChanged(callback, signal) {
|
|
51
|
+
assertChromeStorageAvailable();
|
|
45
52
|
const changeHandler = (changes, area) => {
|
|
46
53
|
if (area !== this.areaName) {
|
|
47
54
|
return;
|
|
48
55
|
}
|
|
49
56
|
for (const rawKey of Object.keys(changes)) {
|
|
50
57
|
const secondaryKey = this.getSecondaryStorageKey(rawKey);
|
|
51
|
-
if (secondaryKey) {
|
|
52
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument -- Assumes the user never uses the Storage API directly
|
|
58
|
+
if (secondaryKey && hasStorageValueChanged(changes[rawKey])) {
|
|
59
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-unsafe-type-assertion -- Assumes the user never uses the Storage API directly
|
|
53
60
|
callback(secondaryKey, changes[rawKey].newValue);
|
|
54
61
|
}
|
|
55
62
|
}
|
|
@@ -7,15 +7,14 @@ export declare class StorageItem<
|
|
|
7
7
|
Base,
|
|
8
8
|
/** The return type will be undefined unless you provide a default value */
|
|
9
9
|
Return = Base | undefined> {
|
|
10
|
-
|
|
10
|
+
#private;
|
|
11
11
|
readonly area: chrome.storage.AreaName;
|
|
12
12
|
readonly defaultValue?: Return;
|
|
13
|
-
|
|
14
|
-
onChange: (callback: (value: Exclude<Return, undefined>) => void, signal?: AbortSignal) => void;
|
|
13
|
+
readonly key: string;
|
|
15
14
|
constructor(key: string, { area, defaultValue, }?: StorageItemOptions<Exclude<Return, undefined>>);
|
|
16
|
-
get
|
|
17
|
-
set
|
|
18
|
-
has
|
|
19
|
-
remove
|
|
15
|
+
get(): Promise<Return>;
|
|
16
|
+
set(value: Exclude<Return, undefined>): Promise<void>;
|
|
17
|
+
has(): Promise<boolean>;
|
|
18
|
+
remove(): Promise<void>;
|
|
20
19
|
onChanged(callback: (value: Exclude<Return, undefined>) => void, signal?: AbortSignal): void;
|
|
21
20
|
}
|
|
@@ -1,46 +1,50 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { assertChromeStorageAvailable, hasStorageValueChanged } from './utils.js';
|
|
2
2
|
export class StorageItem {
|
|
3
|
-
key;
|
|
4
3
|
area;
|
|
5
4
|
defaultValue;
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
key;
|
|
6
|
+
get #storage() {
|
|
7
|
+
assertChromeStorageAvailable();
|
|
8
|
+
return chrome.storage[this.area];
|
|
9
|
+
}
|
|
8
10
|
constructor(key, { area = 'local', defaultValue, } = {}) {
|
|
9
11
|
this.key = key;
|
|
10
12
|
this.area = area;
|
|
11
13
|
this.defaultValue = defaultValue;
|
|
12
14
|
}
|
|
13
|
-
|
|
14
|
-
const result = await
|
|
15
|
+
async get() {
|
|
16
|
+
const result = await this.#storage.get(this.key);
|
|
15
17
|
// Do not use Object.hasOwn() due to https://github.com/RickyMarou/jest-webextension-mock/issues/20
|
|
16
18
|
if (result[this.key] === undefined) {
|
|
19
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- Assumes the user never uses the Storage API directly
|
|
17
20
|
return this.defaultValue;
|
|
18
21
|
}
|
|
19
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-return -- Assumes the user never uses the Storage API directly
|
|
22
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-type-assertion -- Assumes the user never uses the Storage API directly
|
|
20
23
|
return result[this.key];
|
|
21
|
-
}
|
|
22
|
-
|
|
24
|
+
}
|
|
25
|
+
async set(value) {
|
|
23
26
|
// eslint-disable-next-line unicorn/prefer-ternary -- ur rong
|
|
24
27
|
if (value === undefined) {
|
|
25
|
-
await
|
|
28
|
+
await this.#storage.remove(this.key);
|
|
26
29
|
}
|
|
27
30
|
else {
|
|
28
|
-
await
|
|
31
|
+
await this.#storage.set({ [this.key]: value });
|
|
29
32
|
}
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
const result = await
|
|
33
|
+
}
|
|
34
|
+
async has() {
|
|
35
|
+
const result = await this.#storage.get(this.key);
|
|
33
36
|
// Do not use Object.hasOwn() due to https://github.com/RickyMarou/jest-webextension-mock/issues/20
|
|
34
37
|
return result[this.key] !== undefined;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
await
|
|
38
|
-
}
|
|
38
|
+
}
|
|
39
|
+
async remove() {
|
|
40
|
+
await this.#storage.remove(this.key);
|
|
41
|
+
}
|
|
39
42
|
onChanged(callback, signal) {
|
|
43
|
+
assertChromeStorageAvailable();
|
|
40
44
|
const changeHandler = (changes, area) => {
|
|
41
45
|
const changedItem = changes[this.key];
|
|
42
|
-
if (area === this.area && changedItem) {
|
|
43
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument -- Assumes the user never uses the Storage API directly
|
|
46
|
+
if (area === this.area && changedItem && hasStorageValueChanged(changedItem)) {
|
|
47
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-unsafe-type-assertion -- Assumes the user never uses the Storage API directly
|
|
44
48
|
callback(changedItem.newValue);
|
|
45
49
|
}
|
|
46
50
|
};
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
/* eslint-disable @typescript-eslint/
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-unnecessary-type-arguments -- Explicit type arguments are intentional in tsd assertions */
|
|
2
|
+
/* eslint-disable @typescript-eslint/no-restricted-types -- null is an intentional test value */
|
|
2
3
|
/* eslint-disable no-new -- Type tests only */
|
|
3
4
|
import { expectType, expectNotAssignable, expectAssignable } from 'tsd';
|
|
4
5
|
import { StorageItem } from './storage-item.js';
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export function assertChromeStorageAvailable() {
|
|
2
|
+
if (!globalThis.chrome?.storage) {
|
|
3
|
+
throw new TypeError('`chrome.storage` is not available. Make sure you\'re running in a browser extension context.');
|
|
4
|
+
}
|
|
5
|
+
}
|
|
6
|
+
// Workaround for https://github.com/w3c/webextensions/issues/511
|
|
7
|
+
// Firefox fires onChanged even when set() is called with the same value
|
|
8
|
+
export function hasStorageValueChanged(change) {
|
|
9
|
+
// eslint-disable-next-line n/no-unsupported-features/node-builtins -- browser extension context, not Node.js
|
|
10
|
+
return !globalThis.navigator?.userAgent.includes('Firefox')
|
|
11
|
+
|| JSON.stringify(change.newValue) !== JSON.stringify(change.oldValue);
|
|
12
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "webext-storage",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.0.0",
|
|
4
4
|
"description": "A more usable typed storage API for Web Extensions",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"browser",
|
|
@@ -31,27 +31,14 @@
|
|
|
31
31
|
"test:watch": "vitest",
|
|
32
32
|
"watch": "tsc --watch"
|
|
33
33
|
},
|
|
34
|
-
"xo": {
|
|
35
|
-
"envs": [
|
|
36
|
-
"browser"
|
|
37
|
-
],
|
|
38
|
-
"globals": [
|
|
39
|
-
"chrome"
|
|
40
|
-
]
|
|
41
|
-
},
|
|
42
|
-
"dependencies": {
|
|
43
|
-
"webext-polyfill-kinda": "^1.0.2"
|
|
44
|
-
},
|
|
45
34
|
"devDependencies": {
|
|
46
|
-
"@sindresorhus/tsconfig": "^
|
|
47
|
-
"@types/chrome": "^0.
|
|
48
|
-
"@types/sinon-chrome": "^2.2.15",
|
|
35
|
+
"@sindresorhus/tsconfig": "^8.1.0",
|
|
36
|
+
"@types/chrome": "^0.1.39",
|
|
49
37
|
"jest-chrome": "^0.8.0",
|
|
50
|
-
"
|
|
51
|
-
"
|
|
52
|
-
"
|
|
53
|
-
"
|
|
54
|
-
"xo": "^0.60.0"
|
|
38
|
+
"tsd": "^0.33.0",
|
|
39
|
+
"typescript": "^6.0.2",
|
|
40
|
+
"vitest": "^4.1.2",
|
|
41
|
+
"xo": "^2.0.2"
|
|
55
42
|
},
|
|
56
43
|
"engines": {
|
|
57
44
|
"node": ">=20"
|
package/readme.md
CHANGED