Fossil SCM

fossil-scm / src / fossil.storage.js
Blame History Raw 166 lines
1
(function(F){
2
/**
3
fossil.storage is a basic wrapper around localStorage
4
or sessionStorage or a dummy proxy object if neither
5
of those are available.
6
*/
7
const tryStorage = function f(obj){
8
if(!f.key) f.key = 'fossil.access.check';
9
try{
10
obj.setItem(f.key, 'f');
11
const x = obj.getItem(f.key);
12
obj.removeItem(f.key);
13
if(x!=='f') throw new Error(f.key+" failed")
14
return obj;
15
}catch(e){
16
return undefined;
17
}
18
};
19
20
/** Internal storage impl for fossil.storage. */
21
const $storage =
22
tryStorage(window.localStorage)
23
|| tryStorage(window.sessionStorage)
24
|| tryStorage({
25
// A basic dummy xyzStorage stand-in
26
$$$:{},
27
setItem: function(k,v){this.$$$[k]=v},
28
getItem: function(k){
29
return this.$$$.hasOwnProperty(k) ? this.$$$[k] : undefined;
30
},
31
removeItem: function(k){delete this.$$$[k]},
32
clear: function(){this.$$$={}}
33
});
34
35
/**
36
For the dummy storage we need to differentiate between
37
$storage and its real property storage for hasOwnProperty()
38
to work properly...
39
*/
40
const $storageHolder = $storage.hasOwnProperty('$$$') ? $storage.$$$ : $storage;
41
42
/**
43
A prefix which gets internally applied to all fossil.storage
44
property keys so that localStorage and sessionStorage across the
45
same browser profile instance do not "leak" across multiple repos
46
being hosted by the same origin server. Such polination is still
47
there but, with this key prefix applied, it won't be immediately
48
visible via the storage API.
49
50
With this in place we can justify using localStorage instead of
51
sessionStorage again.
52
53
One implication, it was discovered after the release of 2.12, of
54
using localStorage and sessionStorage, is that their scope (the
55
same "origin" and client application/profile) allows multiple
56
repos on the same origin to use the same storage. Thus a user
57
editing a wiki in /repoA/wikiedit could then see those edits in
58
/repoB/wikiedit. The data do not cross user- or browser
59
boundaries, though, so it "might" arguably be called a bug. Even
60
so, it was never intended for that to happen. Rather than lose
61
localStorage access altogether, storageKeyPrefix was added so
62
that we can sandbox that state for the various repos.
63
64
See: https://fossil-scm.org/forum/forumpost/4afc4d34de
65
66
Sidebar: it might seem odd to provide a key prefix and stick all
67
properties in the topmost level of the storage object. We do that
68
because adding a layer of object to sandbox each repo would mean
69
(de)serializing that whole tree on every storage property change
70
(and we update storage often during editing sessions).
71
e.g. instead of storageObject.projectName.foo we have
72
storageObject[storageKeyPrefix+'foo']. That's soley for
73
efficiency's sake (in terms of battery life and
74
environment-internal storage-level effort). Even so, it might (or
75
might not) be useful to do that someday.
76
*/
77
const storageKeyPrefix = (
78
$storageHolder===$storage/*localStorage or sessionStorage*/
79
? (
80
F.config.projectCode || F.config.projectName
81
|| F.config.shortProjectName || window.location.pathname
82
)+'::' : (
83
'' /* transient storage */
84
)
85
);
86
87
/**
88
A proxy for localStorage or sessionStorage or a
89
page-instance-local proxy, if neither one is availble.
90
91
Which exact storage implementation is uses is unspecified, and
92
apps must not rely on it.
93
*/
94
F.storage = {
95
storageKeyPrefix: storageKeyPrefix,
96
/** Sets the storage key k to value v, implicitly converting
97
it to a string. */
98
set: (k,v)=>$storage.setItem(storageKeyPrefix+k,v),
99
/** Sets storage key k to JSON.stringify(v). */
100
setJSON: (k,v)=>$storage.setItem(storageKeyPrefix+k,JSON.stringify(v)),
101
/** Returns the value for the given storage key, or
102
dflt if the key is not found in the storage. */
103
get: (k,dflt)=>$storageHolder.hasOwnProperty(
104
storageKeyPrefix+k
105
) ? $storage.getItem(storageKeyPrefix+k) : dflt,
106
/** Returns true if the given key has a value of "true". If the
107
key is not found, it returns true if the boolean value of dflt
108
is "true". (Remember that JS persistent storage values are all
109
strings.) */
110
getBool: function(k,dflt){
111
return 'true'===this.get(k,''+(!!dflt));
112
},
113
/** Returns the JSON.parse()'d value of the given
114
storage key's value, or dflt is the key is not
115
found or JSON.parse() fails. */
116
getJSON: function f(k,dflt){
117
try {
118
const x = this.get(k,f);
119
return x===f ? dflt : JSON.parse(x);
120
}
121
catch(e){return dflt}
122
},
123
/** Returns true if the storage contains the given key,
124
else false. */
125
contains: (k)=>$storageHolder.hasOwnProperty(storageKeyPrefix+k),
126
/** Removes the given key from the storage. Returns this. */
127
remove: function(k){
128
$storage.removeItem(storageKeyPrefix+k);
129
return this;
130
},
131
/** Clears ALL keys from the storage. Returns this. */
132
clear: function(){
133
this.keys().forEach((k)=>$storage.removeItem(/*w/o prefix*/k));
134
return this;
135
},
136
/** Returns an array of all keys currently in the storage. */
137
keys: ()=>Object.keys($storageHolder).filter((v)=>(v||'').startsWith(storageKeyPrefix)),
138
/** Returns true if this storage is transient (only available
139
until the page is reloaded), indicating that fileStorage
140
and sessionStorage are unavailable. */
141
isTransient: ()=>$storageHolder!==$storage,
142
/** Returns a symbolic name for the current storage mechanism. */
143
storageImplName: function(){
144
if($storage===window.localStorage) return 'localStorage';
145
else if($storage===window.sessionStorage) return 'sessionStorage';
146
else return 'transient';
147
},
148
149
/**
150
Returns a brief help text string for the currently-selected
151
storage type.
152
*/
153
storageHelpDescription: function(){
154
return {
155
localStorage: "Browser-local persistent storage with an "+
156
"unspecified long-term lifetime (survives closing the browser, "+
157
"but maybe not a browser upgrade).",
158
sessionStorage: "Storage local to this browser tab, "+
159
"lost if this tab is closed.",
160
"transient": "Transient storage local to this invocation of this page."
161
}[this.storageImplName()];
162
}
163
};
164
165
})(window.fossil);
166

Keyboard Shortcuts

Open search /
Next entry (timeline) j
Previous entry (timeline) k
Open focused entry Enter
Show this help ?
Toggle theme Top nav button