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
-502
-503
-504
-505
-506
-507
-508
-509
-510
-511
-512
-513
-514
-515
-516
-517
-518
-519
-520
-521
-522
-523
-524
-525
-526
-527
-528
-529
-530
-531
-532
-533
-534
-535
-536
-537
-538
-539
-540
-541
-542
-543
-544
-545
-546
-547
-548
-549
-550
-551
-552
-553
-554
-555
-556
-557
-558
-559
-560
-561
-562
-563
-564
-565
-566
-567
-568
-569
-570
-571
-572
-573
-574
-575
-576
-577
-578
-579
-580
-581
-582
-583
-584
-585
-586
-587
-588
-589
-590
-591
-592
-593
-594
-595
-596
-597
-598
-599
-600
-601
-602
-603
-604
-605
-606
-607
-608
-609
-610
-611
-612
-613
-614
-615
-616
-617
-618
-619
-620
-621
-622
-623
-624
-625
-626
-627
-628
-629
-630
-631
-632
-633
-634
-635
-636
-637
-638
-639
-640
-641
-642
-643
-644
-645
-646
-647
-648
-649
-650
-651
-652
-653
-654
-655
-656
-657
-658
-659
-660
-661
-662
-663
-664
-665
-666
-667
-668
-669
-670
-671
-672
-673
-674
-675
-676
-677
-678
-679
-680
-681
-682
-683
-684
-685
-686
-687
-688
-689
-690
-691
-692
-693
-694
-695
-696
-697
-698
-699
-700
-701
-702
-703
-704
-705
-706
-707
-708
-709
-710
-711
-712
-713
-714
-715
-716
-717
-718
-719
-720
-721
-722
-723
-724
-725
-726
-727
-728
-729
-730
-731
-732
-733
-734
-735
-736
-737
-738
-739
-740
-741
-742
-743
-744
-745
-746
-747
-748
-749
-750
-751
-752
-753
-754
-755
-756
-757
-758
-759
-760
-761
-762
-763
-764
-765
-766
-767
-768
-769
-770
-771
-772
-773
-774
-775
-776
-777
-778
-779
-780
-781
-782
-783
-784
-785
-786
-787
-788
-789
-790
-791
-792
-793
-794
-795
-796
-797
-798
-799
-800
-801
-802
-803
-804
-805
-806
-807
-808
-809
-810
-811
-812
-813
-814
-815
-816
-817
-818
-819
-820
-821
-822
-823
-824
-825
-826
-827
-828
-829
-830
-831
-832
-833
-834
-835
-836
-837
-838
-839
-840
-841
-842
-843
-844
-845
-846
-847
-848
-849
-850
-851
-852
-853
-854
-855
-856
-857
-858
-859
-860
-861
-862
-863
-864
-865
-866
-867
-868
-869
-870
-871
-872
-873
-874
-875
-876
-877
-878
-879
-880
-881
-882
-883
-884
-885
-886
-887
-888
-889
-890
-891
-892
-893
-894
-895
-896
-897
-898
-899
-900
-901
-902
-903
-904
-905
-906
-907
-908
-909
-910
-911
-912
-913
-914
-915
-916
-917
-918
-919
-920
-921
-922
-923
-924
-925
-926
-927
-928
-929
-930
-931
-932
-933
-934
-935
-936
-937
-938
-939
-940
-941
-942
-943
-944
-945
-946
-947
-948
-949
-950
-951
-952
-953
-954
-955
-956
-957
-958
-959
-960
-961
-962
-963
-964
-965
-966
-967
-968
-969
-970
-971
-972
-973
-974
-975
-976
-977
-978
-979
-980
-981
-982
-983
-984
-985
-986
-987
-988
-989
-990
-991
-992
-993 | 1
-1
-
-1
-
-
-1
-
-
-
-
-
-1
-1097
-
-
-1097
-1097
-
-
-9
-
-
-
-
-
-1088
-
-
-1088
-
-
-
-
-
-1
-
-1
-
-
-
-
-52
-52
-52
-
-
-
-
-
-
-
-
-
-316
-316
-4
-4
-4
-4
-
-
-
-
-
-1
-
-
-
-
-
-
-
-
-1
-47
-47
-376
-
-47
-
-
-1
-
-
-
-1
-
-
-
-
-
-1
-
-1
-766
-
-
-1
-463
-463
-7
-
-
-
-1
-
-
-
-
-
-
-1
-303
-303
-1
-
-
-
-
-
-
-
-1
-
-24
-
-23
-
-
-23
-1
-
-
-
-
-
-22
-
-
-
-
-
-
-
-23
-23
-
-23
-
-
-1
-1
-
-
-
-
-
-
-
-1
-17
-
-
-17
-
-17
-
-
-1
-
-
-
-1
-
-1
-
-17
-
-
-
-
-
-
-
-
-17
-
-
-17
-
-
-17
-
-
-
-
-
-
-
-
-
-
-1
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-1
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-1
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-1
-
-
-
-
-
-
-
-
-1
-
-7
-
-6
-6
-
-
-6
-6
-6
-
-3
-
-2
-2
-
-
-1
-
-
-6
-
-1
-
-
-6
-
-6
-
-
-
-
-
-
-
-
-
-
-
-1
-
-
-
-
-
-
-
-
-
-
-
-
-
-1
-1
-1
-552
-552
-552
-552
-552
-
-
-
-
-
-
-
-
-1
-33
-33
-33
-
-1
-
-
-
-
-
-
-
-1
-
-71
-2
-
-69
-
-69
-
-1
-
-1
-
-1
-69
-69
-
-69
-69
-49
-
-2
-2
-
-
-47
-
-
-69
-
-1
-
-71
-47
-
-71
-
-
-
-
-
-
-
-
-
-
-
-
-
-1
-
-8
-8
-
-2
-
-2
-
-
-8
-
-1
-
-1
-
-1
-8
-8
-
-8
-8
-3
-
-2
-
-2
-1
-1
-
-2
-
-
-1
-1
-
-
-8
-
-1
-8
-
-
-
-
-
-
-
-
-
-
-
-
-
-1
-
-10
-10
-1
-
-
-9
-2
-
-
-7
-
-7
-5
-
-7
-
-7
-
-1
-
-3
-3
-1
-1
-
-2
-
-2
-1
-1
-
-1
-
-
-1
-
-1
-7
-7
-
-7
-
-7
-
-
-
-7
-
-
-7
-3
-
-2
-2
-2
-2
-
-
-1
-1
-
-
-7
-
-1
-10
-
-
-
-
-
-
-
-
-
-
-
-
-
-1
-12
-12
-
-12
-12
-
-12
-
-
-
-12
-
-
-
-
-
-1
-1
-
-1
-12
-
-
-1
-
-12
-
-
-552
-
-
-
-
-
-552
-552
-690
-606
-606
-
-
-
-
-
-552
-552
-552
-475
-
-
-
-
-77
-
-
-
-
-1
-12
-12
-
-
-12
-
-12
-12
-
-
-
-
-1
-
-1
-
-
-
-
-
-
-1
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-1
-1
-1
-1
-1
-1
-1
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-1
-
-
-
-
-
-
-
-
-
-
-
-1
-
-
-
-
-
-
-
-
-
-
-
-
-1
-
-
-
-
-
-
-
-
-
-
-
-1
-
-
-
-
-
-
-
-
-
-
-1
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-1
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-1
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-1
-
-58
-
-15
-15
-
-15
-2
-
-
-58
-
-
-1
-1
-
-1
-
-304
-303
-
-302
-302
-
-302
-
-
-1
-
-18
-17
-17
-
-17
-
-
-1
-
-27
-
-26
-
-
-1
-
-74
-73
-
-
-1
-
-
-14
-
-
-14
-
-
-14
-14
-
-
-752
-752
-752
-177
-
-
-
-
-
-14
-
-
-2
-2
-2
-1
-
-
-14
-2
-
-
-
-14
-
-
-1
-15
-15
-15
-15
-228
-
-
-15
-
-
-1
-
-
-
-
-1
-
-
-
-
-
-1
-8
-8
-8
-
-8
-8
-
-8
-8
-8
-8
-
-1
-
-1
-2
-
-
-1
-1
-
-
-1
-2
-
-
-1
-1
-1
-
-1
-
-
-
-
-
-
-
-
-1
-
-9
-
-8
-8
-8
-8
-
-
-1
-
-
-
-
-
-
-1
-
-
- | (function (root, factory) {
- Iif (typeof define === "function" && define.amd) {
- define(factory);
- } else Iif (typeof exports === "object") {
- module.exports = factory();
- } else {
- root.Asteroid = factory();
- }
-}(this, function () {
-
-"use strict";
-
-function clone (obj) {
- Iif (typeof EJSON !== "undefined") {
- return EJSON.clone(obj);
- }
- var type = typeof obj;
- switch (type) {
- case "undefined":
- case "function":
- return undefined;
- case "string":
- case "number":
- case "boolean":
- return obj;
- case "object":
- Iif (obj === null) {
- return null;
- }
- return JSON.parse(JSON.stringify(obj));
- default:
- return;
- }
-}
-
-var EventEmitter = function () {};
-
-EventEmitter.prototype = {
-
- constructor: EventEmitter,
-
- on: function (name, handler) {
- if (!this._events) this._events = {};
- this._events[name] = this._events[name] || [];
- this._events[name].push(handler);
- },
-
- off: function (name, handler) {
- if (!this._events) this._events = {};
- if (!this._events[name]) return;
- this._events[name].splice(this._events[name].indexOf(handler), 1);
- },
-
- _emit: function (name /* , arguments */) {
- if (!this._events) this._events = {};
- if (!this._events[name]) return;
- var args = arguments;
- var self = this;
- this._events[name].forEach(function (handler) {
- handler.apply(self, Array.prototype.slice.call(args, 1));
- });
- }
-
-};
-
-function formQs (obj) {
- var qs = "";
- for (var key in obj) {
- qs += key + "=" + obj[key] + "&";
- }
- qs = qs.slice(0, -1);
- return qs;
-}
-
-function guid () {
- var ret = "";
- for (var i=0; i<8; i++) {
- ret += Math.floor((1 + Math.random()) * 0x10000).toString(16).substring(1);
- }
- return ret;
-}
-
-function isEmail (string) {
- return string.indexOf("@") !== -1;
-}
-
-function isEqual (obj1, obj2) {
- var str1 = JSON.stringify(obj1);
- var str2 = JSON.stringify(obj2);
- return str1 === str2;
-}
-
-var must = {};
-
-must._toString = function (thing) {
- return Object.prototype.toString.call(thing).slice(8, -1);
-};
-
-must.beString = function (s) {
- var type = this._toString(s);
- if (type !== "String") {
- throw new Error("Assertion failed: expected String, instead got " + type);
- }
-};
-
-must.beArray = function (o) {
- var type = this._toString(o);
- if (type !== "Array") {
- throw new Error("Assertion failed: expected Array, instead got " + type);
- }
-};
-
-must.beObject = function (o) {
- var type = this._toString(o);
- if (type !== "Object") {
- throw new Error("Assertion failed: expected Object, instead got " + type);
- }
-};
-
-//////////////////////////
-// Asteroid constructor //
-//////////////////////////
-
-var Asteroid = function (host, ssl, debug) {
- // Assert arguments type
- must.beString(host);
- // Configure the instance
- this._host = (ssl ? "https://" : "http://") + host;
- // If SockJS is available, use it, otherwise, use WebSocket
- // Note: SockJS is required for IE9 support
- if (window.SockJS) {
- this._ddpOptions = {
- endpoint: (ssl ? "https://" : "http://") + host + "/sockjs",
- SocketConstructor: window.SockJS,
- debug: debug
- };
- } else {
- this._ddpOptions = {
- endpoint: (ssl ? "wss://" : "ws://") + host + "/websocket",
- SocketConstructor: window.WebSocket,
- debug: debug
- };
- }
-
- // Reference containers
- this.collections = {};
- this.subscriptions = {};
- // Init the instance
- this._init();
-};
-// Asteroid instances are EventEmitter-s
-Asteroid.prototype = Object.create(EventEmitter.prototype);
-Asteroid.prototype.constructor = Asteroid;
-
-
-
-////////////////////////////////
-// Establishes the connection //
-////////////////////////////////
-
-Asteroid.prototype._init = function () {
- var self = this;
- // Creates the DDP instance, that will automatically
- // connect to the DDP server.
- self.ddp = new DDP(this._ddpOptions);
- // Register handlers
- self.ddp.on("connected", function () {
- // Upon connection, try resuming the login
- // Save the pormise it returns
- self.resumeLoginPromise = self._tryResumeLogin();
- // Subscribe to the meteor.loginServiceConfiguration
- // collection, which holds the configuration options
- // to login via third party services (oauth).
- self.ddp.sub("meteor.loginServiceConfiguration");
- // Emit the connected event
- self._emit("connected");
- });
- self.ddp.on("reconnected", function () {
- // Upon reconnection, try resuming the login
- // Save the pormise it returns
- self.resumeLoginPromise = self._tryResumeLogin();
- // Re-establish all previously established (and still active) subscriptions
- self._reEstablishSubscriptions();
- // Emit the reconnected event
- self._emit("reconnected");
- });
- self.ddp.on("added", function (data) {
- self._onAdded(data);
- });
- self.ddp.on("changed", function (data) {
- self._onChanged(data);
- });
- self.ddp.on("removed", function (data) {
- self._onRemoved(data);
- });
-};
-
-
-
-///////////////////////////////////////
-// Handler for the ddp "added" event //
-///////////////////////////////////////
-
-Asteroid.prototype._onAdded = function (data) {
- // Get the name of the collection
- var cName = data.collection;
- // If the collection does not exist yet, create it
- if (!this.collections[cName]) {
- this.collections[cName] = new Asteroid._Collection(cName, this);
- }
- // data.fields can be undefined if the item added has only
- // the _id field . To avoid errors down the line, ensure item
- // is an object.
- var item = data.fields || {};
- item._id = data.id;
- // Perform the remote insert
- this.collections[cName]._remoteToLocalInsert(item);
-};
-
-
-
-/////////////////////////////////////////
-// Handler for the ddp "removed" event //
-/////////////////////////////////////////
-
-Asteroid.prototype._onRemoved = function (data) {
- // Check the collection exists to avoid exceptions
- if (!this.collections[data.collection]) {
- return;
- }
- // Perform the reomte remove
- this.collections[data.collection]._remoteToLocalRemove(data.id);
-};
-
-
-
-/////////////////////////////////////////
-// Handler for the ddp "changes" event //
-/////////////////////////////////////////
-
-Asteroid.prototype._onChanged = function (data) {
- // Check the collection exists to avoid exceptions
- if (!this.collections[data.collection]) {
- return;
- }
- // data.fields can be undefined if the update only
- // removed some properties in the item. Make sure
- // it's an object
- if (!data.fields) {
- data.fields = {};
- }
- // If there were cleared fields, explicitly set them
- // to undefined in the data.fields object. This will
- // cause those fields to be present in the for ... in
- // loop the remote update method of the collection
- // performs, causing then the fields to be actually
- // cleared from the item
- if (data.cleared) {
- data.cleared.forEach(function (key) {
- data.fields[key] = undefined;
- });
- }
- // Perform the remote update
- this.collections[data.collection]._remoteToLocalUpdate(data.id, data.fields);
-};
-
-
-
-
-
-
-
-////////////////////////////
-// Call and apply methods //
-////////////////////////////
-
-Asteroid.prototype.call = function (method /* , param1, param2, ... */) {
- // Assert arguments type
- must.beString(method);
- // Get the parameters for apply
- var params = Array.prototype.slice.call(arguments, 1);
- // Call apply
- return this.apply(method, params);
-};
-
-Asteroid.prototype.apply = function (method, params) {
- // Assert arguments type
- must.beString(method);
- // If no parameters are given, use an empty array
- Eif (!Array.isArray(params)) {
- params = [];
- }
- // Create the result and updated promises
- var resultDeferred = Q.defer();
- var updatedDeferred = Q.defer();
- var onResult = function (err, res) {
- // The onResult handler takes care of errors
- if (err) {
- // If errors ccur, reject both promises
- resultDeferred.reject(err);
- updatedDeferred.reject();
- } else {
- // Otherwise resolve the result one
- resultDeferred.resolve(res);
- }
- };
- var onUpdated = function () {
- // Just resolve the updated promise
- updatedDeferred.resolve();
- };
- // Perform the method call
- this.ddp.method(method, params, onResult, onUpdated);
- // Return an object containing both promises
- return {
- result: resultDeferred.promise,
- updated: updatedDeferred.promise
- };
-};
-
-
-
-/////////////////////
-// Syntactic sugar //
-/////////////////////
-
-Asteroid.prototype.createCollection = function (name) {
- // Assert arguments type
- must.beString(name);
- // Only create the collection if it doesn't exist
- if (!this.collections[name]) {
- this.collections[name] = new Asteroid._Collection(name, this);
- }
- return this.collections[name];
-};
-
-///////////////////////////////////////////
-// Removal and update suffix for backups //
-///////////////////////////////////////////
-
-var mf_removal_suffix = "__del__";
-var mf_update_suffix = "__upd__";
-var is_backup = function (id) {
- var l1 = mf_removal_suffix.length;
- var l2 = mf_update_suffix.length;
- var s1 = id.slice(-1 * l1);
- var s2 = id.slice(-1 * l2);
- return s1 === mf_removal_suffix || s2 === mf_update_suffix;
-};
-
-
-
-/////////////////////////////////////////////
-// Collection class constructor definition //
-/////////////////////////////////////////////
-
-var Collection = function (name, asteroidRef) {
- this.name = name;
- this.asteroid = asteroidRef;
- this._set = new Set();
-};
-Collection.prototype.constructor = Collection;
-
-
-
-///////////////////////////////////////////////
-// Insert-related private and public methods //
-///////////////////////////////////////////////
-
-Collection.prototype._localToLocalInsert = function (item) {
- // If an item by that id already exists, raise an exception
- if (this._set.contains(item._id)) {
- throw new Error("Item " + item._id + " already exists");
- }
- this._set.put(item._id, item);
- // Return a promise, just for api consistency
- return Q(item._id);
-};
-Collection.prototype._remoteToLocalInsert = function (item) {
- // The server is the SSOT, add directly
- this._set.put(item._id, item);
-};
-Collection.prototype._localToRemoteInsert = function (item) {
- var self = this;
- var deferred = Q.defer();
- // Construct the name of the method we need to call
- var methodName = "/" + self.name + "/insert";
- self.asteroid.ddp.method(methodName, [item], function (err, res) {
- if (err) {
- // On error restore the database and reject the promise
- self._set.del(item._id);
- deferred.reject(err);
- } else {
- // Else resolve the promise
- deferred.resolve(item._id);
- }
- });
- return deferred.promise;
-};
-Collection.prototype.insert = function (item) {
- // If the time has no id, generate one for it
- if (!item._id) {
- item._id = guid();
- }
- return {
- // Perform the local insert
- local: this._localToLocalInsert(item),
- // Send the insert request
- remote: this._localToRemoteInsert(item)
- };
-};
-
-
-
-///////////////////////////////////////////////
-// Remove-related private and public methods //
-///////////////////////////////////////////////
-
-Collection.prototype._localToLocalRemove = function (id) {
- // Check if the item exists in the database
- var existing = this._set.get(id);
- if (existing) {
- // Create a backup of the object to delete
- this._set.put(id + mf_removal_suffix, existing);
- // Delete the object
- this._set.del(id);
- }
- // Return a promise, just for api consistency
- return Q(id);
-};
-Collection.prototype._remoteToLocalRemove = function (id) {
- // The server is the SSOT, remove directly (item and backup)
- this._set.del(id);
-};
-Collection.prototype._localToRemoteRemove = function (id) {
- var self = this;
- var deferred = Q.defer();
- // Construct the name of the method we need to call
- var methodName = "/" + self.name + "/remove";
- self.asteroid.ddp.method(methodName, [{_id: id}], function (err, res) {
- if (err) {
- // On error restore the database and reject the promise
- var backup = self._set.get(id + mf_removal_suffix);
- // Ensure there is a backup
- if (backup) {
- self._set.put(id, backup);
- self._set.del(id + mf_removal_suffix);
- }
- deferred.reject(err);
- } else {
- // Else, delete the (possible) backup and resolve the promise
- self._set.del(id + mf_removal_suffix);
- deferred.resolve(id);
- }
- });
- return deferred.promise;
-};
-Collection.prototype.remove = function (id) {
- return {
- // Perform the local remove
- local: this._localToLocalRemove(id),
- // Send the remove request
- remote: this._localToRemoteRemove(id)
- };
-};
-
-
-
-///////////////////////////////////////////////
-// Update-related private and public methods //
-///////////////////////////////////////////////
-
-Collection.prototype._localToLocalUpdate = function (id, fields) {
- // Ensure the item actually exists
- var existing = this._set.get(id);
- if (!existing) {
- throw new Error("Item " + id + " doesn't exist");
- }
- // Ensure the _id property won't get modified
- if (fields._id && fields._id !== id) {
- throw new Error("Modifying the _id of a document is not allowed");
- }
- // Create a backup
- this._set.put(id + mf_update_suffix, existing);
- // Perform the update
- for (var field in fields) {
- existing[field] = fields[field];
- }
- this._set.put(id, existing);
- // Return a promise, just for api consistency
- return Q(id);
-};
-Collection.prototype._remoteToLocalUpdate = function (id, fields) {
- // Ensure the item exixts in the database
- var existing = this._set.get(id);
- if (!existing) {
- console.warn("Server misbehaviour: item " + id + " doesn't exist");
- return;
- }
- for (var field in fields) {
- // Ensure the server is not trying to moify the item _id
- if (field === "_id" && fields._id !== id) {
- console.warn("Server misbehaviour: modifying the _id of a document is not allowed");
- return;
- }
- existing[field] = fields[field];
- }
- // Perform the update
- this._set.put(id, existing);
-};
-Collection.prototype._localToRemoteUpdate = function (id, fields) {
- var self = this;
- var deferred = Q.defer();
- // Construct the name of the method we need to call
- var methodName = "/" + self.name + "/update";
- // Construct the selector
- var sel = {
- _id: id
- };
- // Construct the modifier
- var mod = {
- $set: fields
- };
- self.asteroid.ddp.method(methodName, [sel, mod], function (err, res) {
- if (err) {
- // On error restore the database and reject the promise
- var backup = self._set.get(id + mf_update_suffix);
- self._set.put(id, backup);
- self._set.del(id + mf_update_suffix);
- deferred.reject(err);
- } else {
- // Else, delete the (possible) backup and resolve the promise
- self._set.del(id + mf_update_suffix);
- deferred.resolve(id);
- }
- });
- return deferred.promise;
-};
-Collection.prototype.update = function (id, fields) {
- return {
- // Perform the local update
- local: this._localToLocalUpdate(id, fields),
- // Send the update request
- remote: this._localToRemoteUpdate(id, fields)
- };
-};
-
-
-
-//////////////////////////////
-// Reactive queries methods //
-//////////////////////////////
-
-var ReactiveQuery = function (set) {
- var self = this;
- self.result = [];
-
- self._set = set;
- self._getResult();
-
- self._set.on("put", function (id) {
- self._getResult();
- self._emit("change", id);
- });
- self._set.on("del", function (id) {
- self._getResult();
- self._emit("change", id);
- });
-
-};
-ReactiveQuery.prototype = Object.create(EventEmitter.prototype);
-ReactiveQuery.constructor = ReactiveQuery;
-
-ReactiveQuery.prototype._getResult = function () {
- this.result = this._set.toArray();
-};
-
-var getFilterFromSelector = function (selector) {
- // Return the filter function
- return function (id, item) {
-
- // Filter out backups
- Iif (is_backup(id)) {
- return false;
- }
-
- // Get the value of the object from a compund key
- // (e.g. "profile.name.first")
- var getItemVal = function (item, key) {
- return key.split(".").reduce(function (prev, curr) {
- if (!prev) return prev;
- prev = prev[curr];
- return prev;
- }, item);
- };
-
- // Iterate all the keys in the selector. The first that
- // doesn't match causes the item to be filtered out.
- for (var key in selector) {
- var itemVal = getItemVal(item, key);
- if (itemVal !== selector[key]) {
- return false;
- }
- }
-
- // At this point the item matches the selector
- return true;
-
- };
-};
-
-Collection.prototype.reactiveQuery = function (selectorOrFilter) {
- var filter;
- Iif (typeof selectorOrFilter === "function") {
- filter = selectorOrFilter;
- } else {
- filter = getFilterFromSelector(selectorOrFilter);
- }
- var subset = this._set.filter(filter);
- return new ReactiveQuery(subset);
-};
-
-
-
-Asteroid._Collection = Collection;
-
-Asteroid.prototype._getOauthClientId = function (serviceName) {
- var loginConfigCollectionName = "meteor_accounts_loginServiceConfiguration";
- var loginConfigCollection = this.collections[loginConfigCollectionName];
- var service = loginConfigCollection.reactiveQuery({service: serviceName}).result[0];
- return service.clientId;
-};
-
-Asteroid.prototype._initOauthLogin = function (service, credentialToken, loginUrl) {
- var popup = window.open(loginUrl, "Login");
- var self = this;
- return Q()
- .then(function () {
- var deferred = Q.defer();
- if (popup.focus) popup.focus();
- var intervalId = setInterval(function () {
- if (popup.closed || popup.closed === undefined) {
- clearInterval(intervalId);
- deferred.resolve();
- }
- }, 100);
- return deferred.promise;
- })
- .then(function () {
- var deferred = Q.defer();
- var loginParameters = {
- oauth: {
- credentialToken: credentialToken
- }
- };
- self.ddp.method("login", [loginParameters], function (err, res) {
- if (err) {
- delete self.userId;
- delete self.loggedIn;
- delete localStorage[self._host + "__login_token__"];
- deferred.reject(err);
- self._emit("loginError", err);
- } else {
- self.userId = res.id;
- self.loggedIn = true;
- localStorage[self._host + "__login_token__"] = res.token;
- self._emit("login", res.id);
- deferred.resolve(res.id);
- }
- });
- return deferred.promise;
- });
-};
-
-Asteroid.prototype._tryResumeLogin = function () {
- var self = this;
- var deferred = Q.defer();
- var token = localStorage[self._host + "__login_token__"];
- Eif (!token) {
- deferred.reject("No login token");
- return deferred.promise;
- }
- var loginParameters = {
- resume: token
- };
- self.ddp.method("login", [loginParameters], function (err, res) {
- if (err) {
- delete self.userId;
- delete self.loggedIn;
- delete localStorage[self._host + "__login_token__"];
- self._emit("loginError", err);
- deferred.reject(err);
- } else {
- self.userId = res.id;
- self.loggedIn = true;
- localStorage[self._host + "__login_token__"] = res.token;
- self._emit("login", res.id);
- deferred.resolve(res.id);
- }
- });
- return deferred.promise;
-};
-
-Asteroid.prototype.loginWithFacebook = function (scope) {
- var credentialToken = guid();
- var query = {
- client_id: this._getOauthClientId("facebook"),
- redirect_uri: this._host + "/_oauth/facebook?close",
- state: credentialToken,
- scope: scope || "email"
- };
- var loginUrl = "https://www.facebook.com/dialog/oauth?" + formQs(query);
- return this._initOauthLogin("facebook", credentialToken, loginUrl);
-};
-
-Asteroid.prototype.loginWithGoogle = function (scope) {
- var credentialToken = guid();
- var query = {
- response_type: "code",
- client_id: this._getOauthClientId("google"),
- redirect_uri: this._host + "/_oauth/google?close",
- state: credentialToken,
- scope: scope || "openid email"
- };
- var loginUrl = "https://accounts.google.com/o/oauth2/auth?" + formQs(query);
- return this._initOauthLogin("google", credentialToken, loginUrl);
-};
-
-Asteroid.prototype.loginWithGithub = function (scope) {
- var credentialToken = guid();
- var query = {
- client_id: this._getOauthClientId("github"),
- redirect_uri: this._host + "/_oauth/github?close",
- state: credentialToken,
- scope: scope || "email"
- };
- var loginUrl = "https://github.com/login/oauth/authorize?" + formQs(query);
- return this._initOauthLogin("github", credentialToken, loginUrl);
-};
-
-Asteroid.prototype.loginWithTwitter = function (scope) {
- var credentialToken = guid();
- var callbackUrl = this._host + "/_oauth/twitter?close&state=" + credentialToken;
- var query = {
- requestTokenAndRedirect: encodeURIComponent(callbackUrl),
- state: credentialToken
- };
- var loginUrl = this._host + "/_oauth/twitter/?" + formQs(query);
- return this._initOauthLogin("twitter", credentialToken, loginUrl);
-};
-
-Asteroid.prototype.createUser = function (usernameOrEmail, password, profile) {
- var self = this;
- var deferred = Q.defer();
- var options = {
- username: isEmail(usernameOrEmail) ? undefined : usernameOrEmail,
- email: isEmail(usernameOrEmail) ? usernameOrEmail : undefined,
- password: password,
- profile: profile
- };
- self.ddp.method("createUser", [options], function (err, res) {
- if (err) {
- self._emit("createUserError", err);
- deferred.reject(err);
- } else {
- self.userId = res.id;
- self.loggedIn = true;
- localStorage[self._host + "__login_token__"] = res.token;
- self._emit("createUser", res.id);
- self._emit("login", res.id);
- deferred.resolve(res.id);
- }
- });
- return deferred.promise;
-};
-
-Asteroid.prototype.loginWithPassword = function (usernameOrEmail, password) {
- var self = this;
- var deferred = Q.defer();
- var loginParameters = {
- password: password,
- user: {
- username: isEmail(usernameOrEmail) ? undefined : usernameOrEmail,
- email: isEmail(usernameOrEmail) ? usernameOrEmail : undefined
- }
- };
- self.ddp.method("login", [loginParameters], function (err, res) {
- if (err) {
- delete self.userId;
- delete self.loggedIn;
- delete localStorage[self._host + "__login_token__"];
- deferred.reject(err);
- self._emit("loginError", err);
- } else {
- self.userId = res.id;
- self.loggedIn = true;
- localStorage[self._host + "__login_token__"] = res.token;
- self._emit("login", res.id);
- deferred.resolve(res.id);
- }
- });
- return deferred.promise;
-};
-
-Asteroid.prototype.logout = function () {
- var self = this;
- var deferred = Q.defer();
- self.ddp.method("logout", [], function (err, res) {
- if (err) {
- self._emit("logoutError", err);
- deferred.reject(err);
- } else {
- delete self.userId;
- delete self.loggedIn;
- delete localStorage[self._host + "__login_token__"];
- self._emit("logout");
- deferred.resolve();
- }
- });
- return deferred.promise;
-};
-
-var Set = function (readonly) {
- // Allow readonly sets
- if (readonly) {
- // Make the put and del methods private
- this._put = this.put;
- this._del = this.del;
- // Replace them with a throwy function
- this.put = this.del = function () {
- throw new Error("Attempt to modify readonly set");
- };
- }
- this._items = {};
-};
-// Inherit from EventEmitter
-Set.prototype = Object.create(EventEmitter.prototype);
-Set.constructor = Set;
-
-Set.prototype.put = function (id, item) {
- // Assert arguments type
- must.beString(id);
- must.beObject(item);
- // Save a clone to avoid collateral damage
- this._items[id] = clone(item);
- this._emit("put", id);
- // Return the set instance to allow method chainging
- return this;
-};
-
-Set.prototype.del = function (id) {
- // Assert arguments type
- must.beString(id);
- delete this._items[id];
- this._emit("del", id);
- // Return the set instance to allow method chainging
- return this;
-};
-
-Set.prototype.get = function (id) {
- // Assert arguments type
- must.beString(id);
- // Return a clone to avoid collateral damage
- return clone(this._items[id]);
-};
-
-Set.prototype.contains = function (id) {
- // Assert arguments type
- must.beString(id);
- return !!this._items[id];
-};
-
-Set.prototype.filter = function (belongFn) {
-
- // Creates the subset
- var sub = new Set(true);
-
- // Keep a reference to the _items hash
- var items = this._items;
-
- // Performs the initial puts
- var ids = Object.keys(items);
- ids.forEach(function (id) {
- // Clone the element to avoid
- // collateral damage
- var itemClone = clone(items[id]);
- var belongs = belongFn(id, itemClone);
- if (belongs) {
- sub._items[id] = items[id];
- }
- });
-
- // Listens to the put and del events
- // to automatically update the subset
- this.on("put", function (id) {
- // Clone the element to avoid
- // collateral damage
- var itemClone = clone(items[id]);
- var belongs = belongFn(id, itemClone);
- if (belongs) {
- sub._put(id, items[id]);
- }
- });
- this.on("del", function (id) {
- sub._del(id);
- });
-
- // Returns the subset
- return sub;
-};
-
-Set.prototype.toArray = function () {
- var array = [];
- var items = this._items;
- var ids = Object.keys(this._items);
- ids.forEach(function (id) {
- array.push(items[id]);
- });
- // Return a clone to avoid collateral damage
- return clone(array);
-};
-
-Set.prototype.toHash = function () {
- // Return a clone to avoid collateral damage
- return clone(this._items);
-};
-
-Asteroid.Set = Set;
-
-////////////////////////
-// Subscription class //
-////////////////////////
-
-var Subscription = function (name, params, asteroid) {
- this._name = name;
- this._params = params;
- this._asteroid = asteroid;
- // Subscription promises
- this._ready = Q.defer();
- this.ready = this._ready.promise;
- // Subscribe via DDP
- var or = this._onReady.bind(this);
- var os = this._onStop.bind(this);
- var oe = this._onError.bind(this);
- this.id = asteroid.ddp.sub(name, params, or, os, oe);
-};
-Subscription.constructor = Subscription;
-
-Subscription.prototype.stop = function () {
- this._asteroid.ddp.unsub(this.id);
-};
-
-Subscription.prototype._onReady = function () {
- this._ready.resolve();
-};
-
-Subscription.prototype._onStop = function () {
- delete this._asteroid.subscriptions[this.id];
-};
-
-Subscription.prototype._onError = function (err) {
- Eif (this.ready.isPending()) {
- this._ready.reject(err);
- }
- delete this._asteroid.subscriptions[this.id];
-};
-
-
-
-//////////////////////
-// Subscribe method //
-//////////////////////
-
-Asteroid.prototype.subscribe = function (name /* , param1, param2, ... */) {
- // Assert arguments type
- must.beString(name);
- // Collect arguments into array
- var params = Array.prototype.slice.call(arguments, 1);
- var sub = new Subscription(name, params, this);
- this.subscriptions[sub.id] = sub;
- return sub;
-};
-
-Asteroid.prototype._reEstablishSubscriptions = function () {
- var subs = this.subscriptions;
- for (var id in subs) {
- subs[id] = new Subscription(subs[id]._name, subs[id]._params, this);
- }
-};
-
-return Asteroid;
-
-}));
- |
-