-
Notifications
You must be signed in to change notification settings - Fork 23
/
Copy pathpersistent_session.js
501 lines (409 loc) · 14.8 KB
/
persistent_session.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
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
// This file uses code direct from Meteor's reactive-dict package, mostly from
// this file: https://github.com/meteor/meteor/blob/0ef65cc/packages/reactive-dict/reactive-dict.js
//
// helpers: https://github.com/meteor/meteor/blob/0ef65cc/packages/reactive-dict/reactive-dict.js#L1-L16
var stringify = function (value) {
if (value === undefined)
return 'undefined';
return EJSON.stringify(value);
};
var parse = function (serialized) {
if (serialized === undefined || serialized === 'undefined')
return undefined;
return EJSON.parse(serialized);
};
var changed = function (v) {
v && v.changed();
};
PersistentSession = function (dictName) {
if (_.isString(dictName)) {
this._dictName = dictName;
// when "session", use the existing dict
if (dictName == "session") {
this._dictName = "" // we don't need a name for session
this._dict = oldSession; // we also want to use the global (incase something was set previously)
// not session? create a new dict
} else {
this._dict = new ReactiveDict(dictName);
}
} else {
throw new Error("dictName must be a string");
}
/*
* Used to determine if we need to migrate how the data is stored.
* Each time the data format changes, change this number.
*
* It should match the current major + minor version:
* EG: 0.3 = 3, 1.2 = 12, 2.0 = 20, or for 0.3.x: 3, or 1.x: 10
*
*/
var PSA_DATA_VERSION = 4;
// === INITIALIZE KEY TRACKING ===
this.psKeys = {};
this.psKeyList = [];
this.psaKeys = {};
this.psaKeyList = [];
// initialize default method setting
this.default_method = 'temporary'; // valid options: 'temporary', 'persistent', 'authenticated'
if (Meteor.settings &&
Meteor.settings.public &&
Meteor.settings.public.persistent_session) {
this.default_method = Meteor.settings.public.persistent_session.default_method;
}
var self = this;
// === HOUSEKEEPING ===
/*
* Converts previously stored values into EJSON compatible formats.
*/
function migrateToEJSON() {
if (amplify.store('__PSDATAVERSION__' + self._dictName) >= 1) {
return;
}
var psKeyList = amplify.store('__PSKEYS__' + self._dictName);
var psaKeyList = amplify.store('__PSAKEYS__' + self._dictName);
_.each([psKeyList, psaKeyList], function(list) {
_.each(list, function(key) {
amplify.store(key, EJSON.stringify(amplify.store(key)));
});
});
amplify.store('__PSDATAVERSION__' + self._dictName, 2);
};
function migrate3Xto4X() {
if (amplify.store('__PSDATAVERSION__' + self._dictName) >= PSA_DATA_VERSION) {
return;
}
var psKeyList = amplify.store('__PSKEYS__' + self._dictName);
var psaKeyList = amplify.store('__PSAKEYS__' + self._dictName);
_.each([psKeyList, psaKeyList], function(list) {
_.each(list, function(key) {
var invalid = false;
try {
EJSON.parse(amplify.store(self._dictName+key));
} catch (error) {
//The data is already in the format that we expect
//Unfortunately there is no EJSON.canParse method
invalid = true;
}
if (!invalid) {
var parsed = EJSON.parse(amplify.store(self._dictName+key));
var jsoned = EJSON.toJSONValue(parsed);
amplify.store(self._dictName+key, jsoned);
}
});
});
amplify.store('__PSDATAVERSION__' + self._dictName, 4);
}
if (Meteor.isClient) {
// --- on startup, load persistent data back into meteor session ---
Meteor.startup(function(){
var val;
migrateToEJSON();
migrate3Xto4X();
// persistent data
var psList = amplify.store('__PSKEYS__' + self._dictName);
if ( typeof psList == "object" && psList.length!==undefined ) {
for (var i=0; i<psList.length; i++) {
if (!_.has(self._dict.keys, psList[i])) {
val = self.get(psList[i]);
self.set(psList[i], val, true, false);
}
}
}
// authenticated data
var psaList = amplify.store('__PSAKEYS__' + self._dictName);
if ( typeof psaList == "object" && psaList.length!==undefined ) {
for (var i=0; i<psaList.length; i++) {
if (!_.has(self._dict.keys, psaList[i])) {
val = self.get(psaList[i]);
self.setAuth(psaList[i], val, true, true);
}
}
}
});
};
Tracker.autorun(function () {
// lazy check for accounts-base
if (Meteor.userId) {
var userId = Meteor.userId()
if (userId) {
// user is logged in, leave session in tacted
} else {
// user is unset, clear authencated keys
self.clearAuth()
}
}
});
return this;
};
// === LOCAL STORAGE INTERACTION ===
PersistentSession.prototype.store = function _psStore(type, key, value) {
// use dict name for uniqueness
this.psKeyList = amplify.store('__PSKEYS__' + this._dictName) || [];
this.psaKeyList = amplify.store('__PSAKEYS__' + this._dictName)|| [];
if (type == 'get') {
return amplify.store(this._dictName + key);
} else {
this.psKeyList = _.without(this.psKeyList, key);
this.psaKeyList = _.without(this.psaKeyList, key);
delete this.psKeys[key];
delete this.psaKeys[key];
if (value===undefined || value===null || type=='temporary') {
value = null;
} else if (type=='persistent') {
this.psKeys[key] = EJSON.toJSONValue(value);
this.psKeyList = _.union(this.psKeyList, [key]);
} else if (type=='authenticated') {
this.psaKeys[key] = EJSON.toJSONValue(value);
this.psaKeyList = _.union(this.psaKeyList, [key]);
}
amplify.store('__PSKEYS__', this.psKeyList);
amplify.store('__PSAKEYS__', this.psaKeyList);
amplify.store(this._dictName + key, EJSON.toJSONValue(value));
}
};
// === GET ===
// keep for backwards compability, redirect to this._dict
PersistentSession.prototype.old_get = function (/* arguments */){
return this._dict.get.apply(this._dict, arguments);
};
PersistentSession.prototype.get = function _psGet(key) {
var val = this.old_get(key);
var psVal;
var unparsedPsVal = this.store('get', key);
if (unparsedPsVal !== undefined) {
psVal = EJSON.fromJSONValue(this.store('get', key));
}
/*
* We can't do `return psVal || val;` here, as when psVal = undefined and
* val = 0, it will return undefined, even though 0 is the correct value.
*/
if (psVal === undefined || psVal === null) {
return val;
}
return psVal;
};
// === SET ===
PersistentSession.prototype.old_set = function (/* arguments */){
// defaults to a persistent, non-authenticated variable
return this._dict.set.apply(this._dict, arguments);
};
PersistentSession.prototype.set = function _psSet(keyOrObject, value, persist, auth) {
// Taken from https://github.com/meteor/meteor/blob/107d858/packages/reactive-dict/reactive-dict.js
if ((typeof keyOrObject === 'object') && (value === undefined)) {
this._setObject(keyOrObject, persist, auth);
return;
}
var key = keyOrObject;
var type = 'temporary';
if (persist || (persist===undefined && (this.default_method=='persistent' || this.default_method=='authenticated'))) {
if (auth || (persist===undefined && auth===undefined && this.default_method=='authenticated')) {
type = 'authenticated';
} else {
type = 'persistent';
}
}
this.store(type, key, value);
this.old_set(key, value);
};
// Taken from https://github.com/meteor/meteor/blob/0ef65cc/packages/reactive-dict/reactive-dict.js#L144-L151
// Backwords compat:
PersistentSession.prototype.all = function _psAll() {
if (this._dict.allDeps) {
this._dict.allDeps.depend();
}
var ret = {};
_.each(this._dict.keys, function(value, key) {
ret[key] = parse(value);
});
return ret;
}
PersistentSession.prototype._setObject = function _psSetObject(object, persist, auth) {
var self = this;
_.each(object, function (value, key){
self.set(key, value, persist, auth);
});
};
PersistentSession.prototype._ensureKey = function _psEnsureKey(key) {
var self = this._dict;
if (!(key in self.keyDeps)) {
self.keyDeps[key] = new Tracker.Dependency;
self.keyValueDeps[key] = {};
}
}
// === EQUALS ===
// Taken from https://github.com/meteor/meteor/blob/0ef65cc/packages/reactive-dict/reactive-dict.js#L93-L137
PersistentSession.prototype.equals = function _psEquals(key, value) {
// Mongo.ObjectID is in the 'mongo' package
var ObjectID = null;
if (Package.mongo) {
ObjectID = Package.mongo.Mongo.ObjectID;
}
// We don't allow objects (or arrays that might include objects) for
// .equals, because JSON.stringify doesn't canonicalize object key
// order. (We can make equals have the right return value by parsing the
// current value and using EJSON.equals, but we won't have a canonical
// element of keyValueDeps[key] to store the dependency.) You can still use
// "EJSON.equals(reactiveDict.get(key), value)".
//
// XXX we could allow arrays as long as we recursively check that there
// are no objects
if (typeof value !== 'string' &&
typeof value !== 'number' &&
typeof value !== 'boolean' &&
typeof value !== 'undefined' &&
!(value instanceof Date) &&
!(ObjectID && value instanceof ObjectID) &&
value !== null) {
throw new Error("ReactiveDict.equals: value must be scalar");
}
var serializedValue = stringify(value);
if (Tracker.active) {
this._ensureKey(key);
if (! _.has(this._dict.keyValueDeps[key], serializedValue))
this._dict.keyValueDeps[key][serializedValue] = new Tracker.Dependency;
var isNew = this._dict.keyValueDeps[key][serializedValue].depend();
if (isNew) {
var that = this;
Tracker.onInvalidate(function () {
// clean up [key][serializedValue] if it's now empty, so we don't
// use O(n) memory for n = values seen ever
if (! that._dict.keyValueDeps[key][serializedValue].hasDependents())
delete that._dict.keyValueDeps[key][serializedValue];
});
}
}
var oldValue = this.get(key);
return EJSON.equals(oldValue, value);
};
// === SET TEMPORARY ===
// alias to .set(); sets a non-persistent variable
PersistentSession.prototype.setTemporary = function _psSetTemp(keyOrObject, value) {
this.set(keyOrObject, value, false, false);
};
PersistentSession.prototype.setTemp = function _psSetTemp(keyOrObject, value) {
this.set(keyOrObject, value, false, false);
};
// === SET PERSISTENT ===
// alias to .set(); sets a persistent variable
PersistentSession.prototype.setPersistent = function _psSetPersistent(keyOrObject, value) {
this.set(keyOrObject, value, true, false);
};
// === SET AUTHENTICATED ===
// alias to .set(); sets a persistent variable that will be removed on logout
PersistentSession.prototype.setAuth = function _psSetAuth(keyOrObject, value) {
this.set(keyOrObject, value, true, true);
};
// === MAKE TEMP / PERSISTENT / AUTH ===
// change the type of session var
PersistentSession.prototype.makeTemp = function _psMakeTemp(key) {
this.store('temporary', key);
};
PersistentSession.prototype.makePersistent = function _psMakePersistent(key) {
var val = this.get(key);
this.store('persistent', key, val);
};
PersistentSession.prototype.makeAuth = function _psMakeAuth(key) {
var val = this.get(key);
this.store('authenticated', key, val);
};
// === CLEAR ===
PersistentSession.prototype.old_clear = function (/* arguments */){
return this._dict.clear.apply(this._dict, arguments);
};
// more or less how it's implemented in reactive dict, but add support for removing single or arrays of keys
// Derived from https://github.com/meteor/meteor/blob/0ef65cc/packages/reactive-dict/reactive-dict.js#L153-L167
PersistentSession.prototype.clear = function _psClear(key, list) {
var self = this;
var oldKeys = self._dict.keys;
if ((key === undefined) && (list === undefined)) {
list = oldKeys;
} else if (!(key === undefined)) {
list = [key]
} else {
// list = list
}
// okay, if it was an array of keys, find the old key pairings for reactivity
if (_.isArray(list)){
var oldList = list;
var list = {}
_.each(oldList, function (key) {
list[key] = oldKeys[key];
});
}
_.each(list, function(value, akey) {
self.set(akey, undefined, false, false);
changed(self._dict.keyDeps[akey]);
if (self._dict.keyValueDeps[akey]) {
changed(self._dict.keyValueDeps[akey][value]);
changed(self._dict.keyValueDeps[akey]['undefined']);
}
delete self._dict.keys[akey]; // remove the key
});
// reactive-dict 1.1.0+
if (self._dict.allDeps) {
self._dict.allDeps.changed();
}
};
// === CLEAR TEMP ===
// clears all the temporary keys
PersistentSession.prototype.clearTemp = function _psClearTemp() {
this.clear(undefined, _.keys(_.omit(this._dict.keys, this.psKeys, this.psaKeys)));
};
// === CLEAR PERSISTENT ===
// clears all persistent keys
PersistentSession.prototype.clearPersistent = function _psClearPersistent() {
this.clear(undefined, this.psKeys);
};
// === CLEAR AUTH ===
// clears all authenticated keys
PersistentSession.prototype.clearAuth = function _psClearAuth() {
this.clear(undefined, this.psaKeys);
};
// === UPDATE ===
// updates the value of a session var without changing its type
PersistentSession.prototype.update = function _psUpdate(key, value) {
var persist, auth;
if ( _.indexOf(this.psaKeyList, key) >= 0 ) { auth = true; }
if ( auth || _.indexOf(this.psKeyList, key) >= 0 ) { persist = true; }
this.set(key, value, persist, auth);
};
// === SET DEFAULT ===
PersistentSession.prototype.old_setDefault = function (/* arguments */){
return this._dict.setDefault.apply(this._dict, arguments);
};
PersistentSession.prototype.setDefault = function _psSetDefault(keyOrObject, value, persist, auth) {
var self = this;
if (_.isObject(keyOrObject)) {
_.each(keyOrObject, function(value, key) {
self.setDefault(key, value, persist, auth);
});
return;
}
if ( this.get(keyOrObject) === undefined) {
this.set(keyOrObject, value, persist, auth);
}
};
// === SET DEFAULT TEMP ===
PersistentSession.prototype.setDefaultTemp = function _psSetDefaultTemp(keyOrObject, value) {
if (_.isObject(keyOrObject)) {
value = undefined;
}
this.setDefault(keyOrObject, value, false, false);
};
// === SET DEFAULT PERSISTENT ===
PersistentSession.prototype.setDefaultPersistent = function _psSetDefaultPersistent(keyOrObject, value) {
if (_.isObject(keyOrObject)) {
value = undefined;
}
this.setDefault(keyOrObject, value, true, false);
};
// === SET DEFAULT AUTH ===
PersistentSession.prototype.setDefaultAuth = function _psSetDefaultAuth(keyOrObject, value) {
if (_.isObject(keyOrObject)) {
value = undefined;
}
this.setDefault(keyOrObject, value, true, true);
};
// automatically apply PersistentSession to Session
var oldSession = _.clone(Session);
_.extend(Session, new PersistentSession("session"))