|
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
|
|