forked from gritzko/swarm
/
SharedWebStorage.js
127 lines (111 loc) · 3.5 KB
/
SharedWebStorage.js
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
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
"use strict";
var Spec = require('./Spec');
var Storage = require('./Storage');
/** SharedWebStorage may use localStorage or sessionStorage
* to cache data. The role of ShWS is dual: it may also
* bridge ops from one browser tab/window to another using
* HTML5 onstorage events. */
function SharedWebStorage(id, options) {
this.options = options || {};
this.lstn = {};
this._id = id;
this.tails = {};
this.store = this.options.persistent ?
window.localStorage : window.sessionStorage;
this.loadLog();
this.installListeners();
}
SharedWebStorage.prototype = new Storage();
SharedWebStorage.prototype.isRoot = false;
module.exports = SharedWebStorage;
SharedWebStorage.prototype.onOp = function (spec, value) {
var ti = spec.filter('/#');
var vo = spec.filter('!.');
if (!vo.toString()) {
return; // state, not an op
}
var tail = this.tails[ti];
if (!tail) {
tail = this.tails[ti] = [];
} else if (tail.indexOf(vo)!==-1) {
return; // replay
}
tail.push(vo);
this.emit(spec,value);
};
SharedWebStorage.prototype.installListeners = function () {
var self = this;
function onStorageChange(ev) {
if (Spec.is(ev.key) && ev.newValue) {
self.onOp(new Spec(ev.key), JSON.parse(ev.newValue));
}
}
window.addEventListener('storage', onStorageChange, false);
};
SharedWebStorage.prototype.loadLog = function () {
// scan/sort specs for existing records
var store = this.store;
var ti;
for (var i = 0; i < store.length; i++) {
var key = store.key(i);
if (!Spec.is(key)) { continue; }
var spec = new Spec(key);
if (spec.pattern() !== '/#!.') {
continue; // ops only
}
ti = spec.filter('/#');
var tail = this.tails[ti];
if (!tail) {
tail = this.tails[ti] = [];
}
tail.push(spec.filter('!.'));
}
for (ti in this.tails) {
this.tails[ti].sort();
}
};
SharedWebStorage.prototype.writeOp = function wsOp(spec, value, src) {
var ti = spec.filter('/#');
var vm = spec.filter('!.');
var tail = this.tails[ti] || (this.tails[ti] = []);
tail.push(vm);
var json = JSON.stringify(value);
this.store.setItem(spec, json);
if (this.options.trigger) {
var otherStore = !this.options.persistent ?
window.localStorage : window.sessionStorage;
if (!otherStore.getItem(spec)) {
otherStore.setItem(spec,json);
otherStore.removeItem(spec,json);
}
}
};
SharedWebStorage.prototype.writeState = function wsPatch(spec, state, src) {
var ti = spec.filter('/#');
this.store.setItem(ti, JSON.stringify(state));
var tail = this.tails[ti];
if (tail) {
for(var k=0; k<tail.length; k++) {
this.store.removeItem(ti + tail[k]);
}
delete this.tails[ti];
}
};
SharedWebStorage.prototype.readState = function (spec, callback) {
spec = new Spec(spec);
var ti = spec.filter('/#');
var state = this.store.getItem(ti);
callback(null, (state&&JSON.parse(state)) || null);
};
SharedWebStorage.prototype.readOps = function (ti, callback) {
var tail = this.tails[ti];
var parsed = null;
for(var k=0; tail && k<tail.length; k++) {
var spec = tail[k];
var value = this.store.getItem(ti+spec);
if (!value) {continue;} // it happens
parsed = parsed || {};
parsed[spec] = JSON.parse(value);
}
callback(null, parsed);
};