Skip to content

Commit bd3c79d

Browse files
authored
Merge pull request #28 from Saba-Sabato/chrome-backed-stores
Chrome backed stores
2 parents 9bb13b5 + 52dbb66 commit bd3c79d

File tree

8 files changed

+86
-73
lines changed

8 files changed

+86
-73
lines changed

src/background/index.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
1-
import { storage } from "../storage";
1+
import { get } from "svelte/store";
2+
import { count } from "../storage";
23

34
// Background service workers
45
// https://developer.chrome.com/docs/extensions/mv3/service_workers/
56

67
chrome.runtime.onInstalled.addListener(() => {
7-
storage.get().then(console.log);
8+
console.log(get(count));
89
});
910

1011
// NOTE: If you want to toggle the side panel from the extension's action button,

src/components/Options.svelte

Lines changed: 9 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,19 @@
11
<script lang="ts">
2-
import { storage } from "../storage";
2+
import { onMount } from "svelte";
3+
import { type Writable } from "svelte/store";
34
4-
export let count: number;
5-
let successMessage: string | null = null;
5+
export let count: Writable<number>;
66
7-
function increment() {
8-
count += 1;
9-
}
10-
11-
function decrement() {
12-
count -= 1;
13-
}
14-
15-
function save() {
16-
storage.set({ count }).then(() => {
17-
successMessage = "Options saved!";
18-
19-
setTimeout(() => {
20-
successMessage = null;
21-
}, 1500);
22-
});
23-
}
7+
onMount(() => {
8+
console.log(`Options onMount count=${$count}`);
9+
});
2410
</script>
2511

2612
<div class="container">
27-
<p>Current count: <b>{count}</b></p>
13+
<p>Current count: <b>{$count}</b></p>
2814
<div>
29-
<button on:click={decrement}>-</button>
30-
<button on:click={increment}>+</button>
31-
<button on:click={save}>Save</button>
32-
{#if successMessage}<span class="success">{successMessage}</span>{/if}
15+
<button on:click={() => ($count -= 1)}>-</button>
16+
<button on:click={() => ($count += 1)}>+</button>
3317
</div>
3418
</div>
3519

@@ -52,9 +36,4 @@
5236
button:focus {
5337
background-color: #27ae60;
5438
}
55-
56-
.success {
57-
color: #2ecc71;
58-
font-weight: bold;
59-
}
6039
</style>

src/components/Overlay.svelte

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,6 @@
11
<script lang="ts">
2-
import { onMount } from "svelte";
3-
import { storage } from "../storage";
2+
import { count } from "../storage";
43
import Options from "./Options.svelte";
5-
6-
let count = 0;
7-
8-
onMount(() => {
9-
storage.get().then((storage) => (count = storage.count));
10-
});
114
</script>
125

136
<div class="overlay">

src/content/index.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1+
import { get } from "svelte/store";
12
import Overlay from "../components/Overlay.svelte";
2-
import { storage } from "../storage";
3+
import { count } from "../storage";
34

45
// Content scripts
56
// https://developer.chrome.com/docs/extensions/mv3/content_scripts/
@@ -8,7 +9,7 @@ import { storage } from "../storage";
89
import "./styles.css";
910

1011
// Some JS on the page
11-
storage.get().then(console.log);
12+
console.log(`CONTENT: ${get(count)}`);
1213

1314
// Some svelte component on the page
1415
new Overlay({ target: document.body });

src/options/index.ts

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import Options from "../components/Options.svelte";
2-
import { storage } from "../storage";
2+
import { count } from "../storage";
33

44
// Options
55
// https://developer.chrome.com/docs/extensions/mv3/options/
@@ -8,11 +8,9 @@ function render() {
88
const target = document.getElementById("app");
99

1010
if (target) {
11-
storage.get().then(({ count }) => {
12-
new Options({
13-
target,
14-
props: { count },
15-
});
11+
new Options({
12+
target,
13+
props: { count },
1614
});
1715
}
1816
}

src/popup/index.ts

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import Options from "../components/Options.svelte";
2-
import { storage } from "../storage";
2+
import { count } from "../storage";
33

44
// Action popup
55
// https://developer.chrome.com/docs/extensions/reference/action/
@@ -8,11 +8,9 @@ function render() {
88
const target = document.getElementById("app");
99

1010
if (target) {
11-
storage.get().then(({ count }) => {
12-
new Options({
13-
target,
14-
props: { count },
15-
});
11+
new Options({
12+
target,
13+
props: { count },
1614
});
1715
}
1816
}

src/sidepanel/index.ts

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import Options from "../components/Options.svelte";
2-
import { storage } from "../storage";
2+
import { count } from "../storage";
33

44
// Side panel
55
// https://developer.chrome.com/docs/extensions/reference/sidePanel/
@@ -8,11 +8,9 @@ function render() {
88
const target = document.getElementById("app");
99

1010
if (target) {
11-
storage.get().then(({ count }) => {
12-
new Options({
13-
target,
14-
props: { count },
15-
});
11+
new Options({
12+
target,
13+
props: { count },
1614
});
1715
}
1816
}

src/storage.ts

Lines changed: 58 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,58 @@
1-
type IStorage = {
2-
count: number;
3-
};
4-
5-
const defaultStorage: IStorage = {
6-
count: 0,
7-
};
8-
9-
export const storage = {
10-
get: (): Promise<IStorage> =>
11-
chrome.storage.sync.get(defaultStorage) as Promise<IStorage>,
12-
set: (value: IStorage): Promise<void> => chrome.storage.sync.set(value),
13-
};
1+
import { writable, type Writable } from 'svelte/store';
2+
3+
/**
4+
* Creates a persistent Svelte store backed by Chrome's sync storage.
5+
* @template T The type of the store's value
6+
* @param key The key to use in Chrome's storage
7+
* @param initialValue The initial value of the store
8+
* @returns A writable Svelte store
9+
*/
10+
export function persistentStore<T>(key: string, initialValue: T): Writable<T> {
11+
const store = writable(initialValue);
12+
// Ensure each value is updated exactly once in store and in chrome storage
13+
let storeValueQueue: T[] = [];
14+
let chromeValueQueue: T[] = [];
15+
16+
function watchStore() {
17+
store.subscribe((value) => {
18+
if (chromeValueQueue.length > 0 && value === chromeValueQueue[0]) {
19+
chromeValueQueue.shift();
20+
return;
21+
}
22+
23+
storeValueQueue.push(value);
24+
chrome.storage.sync.set({ [key]: value });
25+
});
26+
}
27+
28+
function watchChrome() {
29+
chrome.storage.sync.onChanged.addListener((changes) => {
30+
if (!(Object.hasOwn(changes, key))) return;
31+
32+
const value = changes[key].newValue as T;
33+
if (storeValueQueue.length > 0 && value === storeValueQueue[0]) {
34+
storeValueQueue.shift();
35+
return;
36+
}
37+
38+
chromeValueQueue.push(value);
39+
store.set(value);
40+
});
41+
}
42+
43+
// Initialize the store with the value from Chrome storage
44+
chrome.storage.sync.get(key).then((result) => {
45+
let value = Object.hasOwn(result, key) ? result[key] : initialValue;
46+
if (!Object.hasOwn(result, key)) {
47+
console.log(`Persistent store: couldn't find key [${key}] in chrome storage. Default to initial value [${initialValue}]`)
48+
}
49+
chromeValueQueue.push(value);
50+
store.set(value);
51+
watchStore();
52+
watchChrome();
53+
});
54+
55+
return store;
56+
}
57+
58+
export const count = persistentStore("count", 10);

0 commit comments

Comments
 (0)