-
Notifications
You must be signed in to change notification settings - Fork 1
/
url-store.ts
97 lines (83 loc) · 2.41 KB
/
url-store.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
import type { Syncer } from "../types";
import { EventEmitter } from "./event-emitter";
import { jsonSerializer } from "./serializer";
import type { Listener, StateSerializer, Store } from "./types";
type URLEncoder = {
encode: (url: string, state?: Record<string, unknown>) => string;
decode: (url: string) => Record<string, unknown>;
};
export function searchParamEncoder(
paramName: string,
stateSerializer: StateSerializer,
): URLEncoder {
return {
encode: (url, state) => {
const newUrl = new URL(url);
if (state) {
newUrl.searchParams.set(paramName, stateSerializer.serialize(state));
} else {
newUrl.searchParams.delete(paramName);
}
return newUrl.toString();
},
decode: (url: string) => {
const { searchParams } = new URL(url);
const value = searchParams.get(paramName);
return value ? stateSerializer.deserialize(value) : {};
},
};
}
export const defaultSearchParamEncoder = searchParamEncoder(
"location-state",
jsonSerializer,
);
export class URLStore implements Store {
private state: Record<string, unknown> = {};
private syncedURL: string | undefined;
private events = new EventEmitter();
constructor(
private readonly syncer: Syncer,
private readonly urlEncoder: URLEncoder = defaultSearchParamEncoder,
) {}
subscribe(name: string, listener: Listener) {
this.events.on(name, listener);
return () => this.events.off(name, listener);
}
get(name: string) {
return this.state[name];
}
set(name: string, value: unknown) {
if (typeof value === "undefined") {
delete this.state[name];
} else {
this.state[name] = value;
}
try {
// save to url
this.syncedURL = this.urlEncoder.encode(location.href, this.state);
this.syncer.updateURL(this.syncedURL);
} catch (e) {
console.error(e);
}
this.events.emit(name);
}
load() {
const currentURL = location.href;
if (currentURL === this.syncedURL) return;
try {
this.state = this.urlEncoder.decode(currentURL);
this.syncedURL = currentURL;
} catch (e) {
console.error(e);
this.state = {};
// remove invalid state from url.
const url = this.urlEncoder.encode(currentURL);
this.syncer.updateURL(url);
this.syncedURL = url;
}
this.events.deferEmitAll();
}
save() {
// `set` to save it in the URL, so it does nothing.
}
}