alchemymvc 1.2.5 → 1.2.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (76) hide show
  1. package/LICENSE +0 -0
  2. package/README.md +0 -0
  3. package/lib/app/assets/scripts/.gitkeep +0 -0
  4. package/lib/app/assets/stylesheets/alchemy-info.less +0 -0
  5. package/lib/app/behaviour/publishable_behaviour.js +0 -0
  6. package/lib/app/behaviour/revision_behaviour.js +0 -0
  7. package/lib/app/behaviour/sluggable_behaviour.js +0 -0
  8. package/lib/app/component/.gitkeep +0 -0
  9. package/lib/app/conduit/electron_conduit.js +0 -0
  10. package/lib/app/conduit/http_conduit.js +173 -173
  11. package/lib/app/conduit/socket_conduit.js +620 -620
  12. package/lib/app/controller/alchemy_info_controller.js +0 -0
  13. package/lib/app/datasource/mongo_datasource.js +0 -0
  14. package/lib/app/helper/client_collection.js +0 -0
  15. package/lib/app/helper/pagination_helper.js +0 -0
  16. package/lib/app/helper/router_helper.js +0 -0
  17. package/lib/app/helper/socket_helper.js +613 -613
  18. package/lib/app/helper_component/paginate_component.js +0 -0
  19. package/lib/app/helper_controller/component.js +0 -0
  20. package/lib/app/helper_controller/conduit.js +0 -0
  21. package/lib/app/helper_controller/controller.js +0 -0
  22. package/lib/app/helper_datasource/00-nosql_datasource.js +0 -0
  23. package/lib/app/helper_datasource/05-fallback_datasource.js +0 -0
  24. package/lib/app/helper_datasource/idb_datasource.js +0 -0
  25. package/lib/app/helper_datasource/indexed_db.js +0 -0
  26. package/lib/app/helper_field/00-objectid_field.js +0 -0
  27. package/lib/app/helper_field/06-text_field.js +0 -0
  28. package/lib/app/helper_field/10-number_field.js +0 -0
  29. package/lib/app/helper_field/boolean_field.js +0 -0
  30. package/lib/app/helper_field/date_field.js +0 -0
  31. package/lib/app/helper_field/datetime_field.js +0 -0
  32. package/lib/app/helper_field/enum_field.js +0 -0
  33. package/lib/app/helper_field/geopoint_field.js +0 -0
  34. package/lib/app/helper_field/habtm_field.js +0 -0
  35. package/lib/app/helper_field/hasoneparent_field.js +0 -0
  36. package/lib/app/helper_field/html_field.js +0 -0
  37. package/lib/app/helper_field/integer_field.js +0 -0
  38. package/lib/app/helper_field/object_field.js +0 -0
  39. package/lib/app/helper_field/regexp_field.js +0 -0
  40. package/lib/app/helper_field/schema_field.js +23 -2
  41. package/lib/app/helper_field/time_field.js +0 -0
  42. package/lib/app/helper_field/url_field.js +0 -0
  43. package/lib/app/helper_model/criteria.js +0 -0
  44. package/lib/app/helper_model/db_query.js +0 -0
  45. package/lib/app/helper_model/document_list.js +0 -0
  46. package/lib/app/model/alchemy_task_model.js +0 -0
  47. package/lib/app/routes.js +0 -0
  48. package/lib/app/view/alchemy/info.ejs +0 -0
  49. package/lib/app/view/error/unknown.ejs +0 -0
  50. package/lib/app/view/paginate/navlist.ejs +0 -0
  51. package/lib/bootstrap.js +0 -0
  52. package/lib/class/behaviour.js +0 -0
  53. package/lib/class/component.js +0 -0
  54. package/lib/class/conduit.js +2555 -2552
  55. package/lib/class/controller.js +4 -1
  56. package/lib/class/document_list.js +0 -0
  57. package/lib/class/helper.js +0 -0
  58. package/lib/class/inode.js +0 -0
  59. package/lib/class/inode_dir.js +0 -0
  60. package/lib/class/inode_file.js +112 -112
  61. package/lib/class/inode_list.js +0 -0
  62. package/lib/class/model.js +1772 -1769
  63. package/lib/class/path_definition.js +0 -0
  64. package/lib/class/route.js +0 -0
  65. package/lib/class/session.js +0 -0
  66. package/lib/class/task.js +0 -0
  67. package/lib/core/base.js +50 -9
  68. package/lib/core/discovery.js +0 -0
  69. package/lib/core/routing.js +0 -0
  70. package/lib/core/socket.js +159 -159
  71. package/lib/init/alchemy.js +1823 -1823
  72. package/lib/init/constants.js +0 -0
  73. package/lib/init/functions.js +8 -4
  74. package/lib/init/load_functions.js +0 -0
  75. package/lib/init/requirements.js +101 -101
  76. package/package.json +74 -74
@@ -1,614 +1,614 @@
1
- /**
2
- * See if the given object is a stream
3
- *
4
- * @author Jelle De Loecker <jelle@develry.be>
5
- * @since 0.2.0
6
- * @version 1.0.5
7
- *
8
- * @return {Boolean}
9
- */
10
- function isStream(obj) {
11
- return obj && (typeof obj._read == 'function' || typeof obj._write == 'function') && typeof obj.on === 'function';
12
- };
13
-
14
- /**
15
- * The Linkup class
16
- *
17
- * @author Jelle De Loecker <jelle@develry.be>
18
- * @since 0.2.0
19
- * @version 0.2.0
20
- *
21
- * @param {String} type
22
- */
23
- var Linkup = Blast.Collection.Function.inherits('Informer', function ClientLinkup(client, type, data) {
24
-
25
- var server_object,
26
- id;
27
-
28
- if (type && typeof type == 'object' && type.id && data == null) {
29
- server_object = type;
30
- id = server_object.id;
31
- type = server_object.type;
32
- data = server_object.data;
33
- } else {
34
- id = type + '-' + Blast.Classes.Crypto.pseudoHex();
35
- }
36
-
37
- // The identifier
38
- this.id = id;
39
-
40
- // The typename of the link
41
- this.type = type;
42
-
43
- // The initial submitted data
44
- this.initialData = data;
45
-
46
- // Make the linkup store itself
47
- client.linkups[this.id] = this;
48
-
49
- // The parent server
50
- this.client = client;
51
-
52
- if (server_object) {
53
- this.submit('ready');
54
- } else {
55
- // Establish the link
56
- client._submit('linkup', {type: type, id: this.id, data: data});
57
- }
58
- });
59
-
60
- /**
61
- * Submit a message to the server on this link
62
- *
63
- * @author Jelle De Loecker <jelle@develry.be>
64
- * @since 0.2.0
65
- * @version 0.2.0
66
- *
67
- * @param {String} type
68
- * @param {Object} data
69
- * @param {Function} callback
70
- */
71
- Linkup.setMethod(function submit(type, data, stream, callback) {
72
- this.client.submit([this.id, type], data, stream, callback);
73
- });
74
-
75
- /**
76
- * Submit a message to the server on this link and return a promise
77
- *
78
- * @author Jelle De Loecker <jelle@elevenways.be>
79
- * @since 1.1.2
80
- * @version 1.1.2
81
- *
82
- * @param {String} type
83
- * @param {Object} data
84
- */
85
- Linkup.setMethod(function demand(type, data, stream) {
86
-
87
- const that = this;
88
-
89
- let pledge = new Classes.Pledge(),
90
- args = [type, data];
91
-
92
- if (stream) {
93
- args.push(stream);
94
- }
95
-
96
- this.submit(...args, function done(err, result) {
97
-
98
- if (err) {
99
- return pledge.reject(err);
100
- }
101
-
102
- pledge.resolve(result);
103
- });
104
-
105
- return pledge;
106
- });
107
-
108
- /**
109
- * Create a stream
110
- *
111
- * @author Jelle De Loecker <jelle@develry.be>
112
- * @since 0.2.0
113
- * @version 0.2.0
114
- */
115
- Linkup.setMethod(function createStream() {
116
- return this.client.createStream();
117
- });
118
-
119
- /**
120
- * Destroy this linkup
121
- *
122
- * @author Jelle De Loecker <jelle@develry.be>
123
- * @since 0.2.0
124
- * @version 0.2.0
125
- */
126
- Linkup.setMethod(function destroy() {
127
- delete this.client.linkups[this.id];
128
- });
129
-
130
- /**
131
- * Actually make the socket.io connection,
132
- * this requires the socket.io js to be loaded
133
- *
134
- * @author Jelle De Loecker <jelle@develry.be>
135
- * @since 0.0.1
136
- * @version 0.2.0
137
- *
138
- * @param {String} address Address to connect to
139
- * @param {Object} data Announcement data
140
- * @param {Function} callback
141
- */
142
- var Client = Blast.Collection.Function.inherits('Informer', function ClientSocket() {
143
-
144
- var that = this;
145
-
146
- // Connected is false
147
- this.connected = false;
148
-
149
- // The packet queue
150
- this.queue = [];
151
-
152
- // The callbacks
153
- this.callbacks = {};
154
-
155
- // Established linkups
156
- this.linkups = {};
157
-
158
- // The client message counter
159
- this.counter = 0;
160
-
161
- // The server object
162
- this.server = null;
163
-
164
- // The server stream
165
- this.serverstream = null;
166
-
167
- // The timesync offset
168
- this.offset = 0;
169
-
170
- // The connection latency
171
- this.latency = 0;
172
-
173
- // Enable auto reconnect
174
- this.reconnect = true;
175
-
176
- // Server-linkup listeners
177
- this.server_linkup_listeners = {};
178
-
179
- this.emitPacket = function emitPacket(packet) {
180
-
181
- var stream;
182
-
183
- if (packet.stream) {
184
- stream = packet.stream;
185
- delete packet.stream;
186
- that.serverstream.emit('payload', stream, packet);
187
- } else {
188
- that.server.emit('payload', packet);
189
- }
190
- };
191
- });
192
-
193
- /**
194
- * Get an offset-corrected timestamp
195
- *
196
- * @author Jelle De Loecker <jelle@develry.be>
197
- * @since 0.2.1
198
- * @version 0.2.1
199
- *
200
- * @return {Number}
201
- */
202
- Client.setMethod(function now() {
203
- return Date.now() + (this.offset || 0);
204
- });
205
-
206
- /**
207
- * Low level socket emit
208
- *
209
- * @author Jelle De Loecker <jelle@develry.be>
210
- * @since 0.2.0
211
- * @version 1.1.0
212
- */
213
- Client.setMethod(function _submit() {
214
- var that = this,
215
- args = Array.cast(arguments);
216
-
217
- this.afterOnce('connected', function connected() {
218
- that.server.emit.apply(that.server, args);
219
- });
220
- });
221
-
222
- /**
223
- * Submit method
224
- *
225
- * @author Jelle De Loecker <jelle@develry.be>
226
- * @since 0.2.0
227
- * @version 0.2.0
228
- *
229
- * @param {String} type
230
- * @param {Object} data
231
- * @param {IOStream} stream
232
- * @param {Function} callback
233
- */
234
- Client.setMethod(function submit(type, data, stream, callback) {
235
-
236
- var packet = {},
237
- regular_stream;
238
-
239
- if (isStream(data)) {
240
- callback = stream;
241
- stream = data;
242
- data = undefined;
243
- } else if (typeof data === 'function') {
244
- callback = data;
245
- data = undefined;
246
- }
247
-
248
- if (!stream || typeof stream == 'function') {
249
- callback = stream;
250
- stream = undefined;
251
- } else if (stream && stream.constructor.name != 'IOStream') {
252
- // Keep the regular stream
253
- regular_stream = stream;
254
-
255
- // Create an IOStream
256
- stream = this.createStream();
257
-
258
- // Pipe the regular stream into the IOStream
259
- regular_stream.pipe(stream);
260
- }
261
-
262
- if (Array.isArray(type)) {
263
- packet.link = type[0];
264
- packet.type = type[1];
265
- } else {
266
- packet.type = type;
267
- }
268
-
269
- if (data && data.constructor.name == 'IOStream') {
270
- stream = data;
271
- packet.noData = true;
272
- } else {
273
- packet.data = data;
274
- }
275
-
276
- packet.id = 'c' + (++this.counter);
277
- packet.stream = stream;
278
-
279
- if (typeof callback == 'function') {
280
- this.callbacks[packet.id] = callback;
281
- packet.respond = true;
282
- }
283
-
284
- if (this.connected) {
285
- this.emitPacket(packet);
286
- } else {
287
- this.queue.push(packet);
288
- }
289
- });
290
-
291
- /**
292
- * Create a stream we can send through a websocket connection
293
- *
294
- * @author Jelle De Loecker <jelle@develry.be>
295
- * @since 0.2.0
296
- * @version 0.2.0
297
- */
298
- Client.setMethod(function createStream() {
299
-
300
- var stream;
301
-
302
- if (Blast.isNode) {
303
- stream = alchemy.use('socket.io-stream').createStream();
304
- } else {
305
- stream = ss.createStream();
306
- }
307
-
308
- return stream;
309
- });
310
-
311
- /**
312
- * Make the actual connection
313
- *
314
- * @author Jelle De Loecker <jelle@develry.be>
315
- * @since 0.2.0
316
- * @version 1.0.5
317
- *
318
- * @param {Function} callback
319
- */
320
- Client.setMethod(function connect(address, data, callback) {
321
-
322
- var that = this,
323
- ioServer,
324
- serverstream,
325
- config = {},
326
- server;
327
-
328
- if (typeof address == 'function') {
329
- callback = address;
330
- data = {};
331
- address = null;
332
- } else if (typeof data == 'function') {
333
- callback = data;
334
- data = {};
335
- }
336
-
337
- if (!data || typeof data != 'object') {
338
- data = {};
339
- }
340
-
341
- if (typeof io != 'undefined') {
342
- ioServer = io;
343
- } else if (typeof alchemy != 'undefined') {
344
- ioServer = alchemy.use('socket.io-client');
345
- } else {
346
- return callback(new Error('Could not find socket.io client library'));
347
- }
348
-
349
- // The address to connect to
350
- this.address = address;
351
-
352
- if (Blast.isNode) {
353
- data.connection_type = 'node';
354
- data.discovery = alchemy.discovery_id;
355
- } else {
356
- data.connection_type = 'browser';
357
- data.scene = hawkejs.scene.sceneId;
358
- data.last_update = alchemy.last_update;
359
- }
360
-
361
- if (!this.reconnect) {
362
- config.reconnection = false;
363
- }
364
-
365
- // Create the connection to the server
366
- if (address) {
367
- server = ioServer.connect(address, config);
368
- } else {
369
- server = ioServer.connect(config);
370
- }
371
-
372
- if (Blast.isNode) {
373
- serverstream = alchemy.use('socket.io-stream')(server);
374
- } else {
375
- serverstream = ss(server);
376
- }
377
-
378
- this.server = server;
379
- this.serverstream = serverstream;
380
-
381
- // Announce ourselves when we've connected
382
- server.on('connect', function onConnect() {
383
- server.emit('announce', data);
384
- });
385
-
386
- // Emit the close event once we get disconnected from the server
387
- server.on('disconnect', function closed() {
388
- that.connected = false;
389
- that.emit('close');
390
- });
391
-
392
- // Listen to timesync commands
393
- server.on('timesync', function gotTimesync(data) {
394
-
395
- // When offset is not defined,
396
- // the server is actually requesting our timestamp
397
- if (data.offset == null) {
398
- data.client_time = Date.now();
399
- server.emit('timesync', data);
400
- return;
401
- }
402
-
403
- // If the offset property is set,
404
- // the timesync procedure has finished
405
-
406
- // Set the values in this object
407
- that.offset = data.offset || 0;
408
- that.latency = data.latency || 0;
409
-
410
- // Emit it as an event, too
411
- that.emit('timesynced', that.offset, that.latency);
412
- });
413
-
414
- // Listen for the ready event
415
- server.on('ready', function onReady() {
416
-
417
- that.connected = true;
418
-
419
- if (callback) {
420
- callback();
421
- }
422
-
423
- that.emit('connected');
424
-
425
- // Emit all the queued packets
426
- that.queue.forEach(that.emitPacket);
427
-
428
- // Request a timesync
429
- that.server.emit('timesync', {start: Date.now()});
430
-
431
- // Reset the queue
432
- that.queue.length = 0;
433
- });
434
-
435
- server.on('error', function(err) {
436
- console.log('Socket error:', err);
437
- });
438
-
439
- // Listen for cookies
440
- server.on('alchemy-set-cookie', function setCookie(data) {
441
- if (Blast.isNode) {
442
- // @TODO: set node cookie?
443
- } else {
444
- hawkejs.scene.cookie(data.name, data.value, data.options);
445
- }
446
- });
447
-
448
- // Listen for server initiated linkups
449
- server.on('linkup', function gotLinkup(config) {
450
-
451
- var linkup = new Linkup(that, config),
452
- i;
453
-
454
- // Look through the server-linkup callbacks
455
- if (that.server_linkup_listeners[config.type]) {
456
- for (i = 0; i < that.server_linkup_listeners[config.type].length; i++) {
457
- that.server_linkup_listeners[config.type][i].call(that, linkup, config.data);
458
- }
459
- }
460
-
461
- // Emit it on the client itself
462
- that.emit(linkup.type, linkup, null);
463
- });
464
-
465
- // Listen for payloads
466
- server.on('payload', onPacket);
467
-
468
- // Listen for payloads with streams
469
- serverstream.on('payload', function onPayload(stream, packet) {
470
- packet.stream = stream;
471
- onPacket(packet);
472
- });
473
-
474
- // Listen for responses with streams
475
- serverstream.on('response', function onStreamingResponse(stream, packet) {
476
- packet.stream = stream;
477
- onResponse(packet);
478
- });
479
-
480
- // Listen for responses
481
- server.on('response', onResponse);
482
-
483
- // The function that handles responses
484
- function onResponse(packet) {
485
-
486
- if (typeof that.callbacks[packet.respond_to] === 'function') {
487
-
488
- if (packet.err && typeof packet.err == 'object') {
489
- try {
490
- packet.err = JSON.undry(packet.err);
491
- } catch (err) {
492
- console.log('Error undrying error:', err, packet);
493
- }
494
- }
495
-
496
- try {
497
- if (packet.data && typeof packet.data == 'object') {
498
- packet.data = JSON.undry(packet.data);
499
- }
500
- } catch (err) {
501
- console.log('ERROR UNDRYING PACKET:', err, packet);
502
- return;
503
- }
504
-
505
- if (packet.noData) {
506
- that.callbacks[packet.respond_to](packet.err, packet.stream);
507
- } else if (packet.stream) {
508
- that.callbacks[packet.respond_to](packet.err, packet.data, packet.stream);
509
- } else {
510
- that.callbacks[packet.respond_to](packet.err, packet.data);
511
- }
512
- }
513
-
514
- delete that.callbacks[packet.respond_to];
515
- }
516
-
517
- // The function that handles packets
518
- function onPacket(packet) {
519
-
520
- var respond;
521
-
522
- try {
523
- if (packet.data && typeof packet.data == 'object') {
524
- packet.data = JSON.undry(packet.data);
525
- }
526
- } catch (err) {
527
- console.log('ERROR UNDRYING PACKET:', err, packet);
528
- return;
529
- }
530
-
531
- if (packet.respond) {
532
- respond = function respond(err, data) {
533
- var responsePacket = {};
534
-
535
- responsePacket.err = err;
536
- responsePacket.respond_to = packet.id;
537
- responsePacket.data = data;
538
-
539
- server.emit('response', responsePacket);
540
- };
541
- }
542
-
543
- // See if this is for a specific linkup
544
- if (packet.link) {
545
- if (that.linkups[packet.link]) {
546
- if (packet.stream) {
547
- that.linkups[packet.link].emit(packet.type, packet.data, packet.stream, respond, null);
548
- } else {
549
- that.linkups[packet.link].emit(packet.type, packet.data, respond, null);
550
- }
551
- }
552
-
553
- return;
554
- }
555
-
556
- if (packet.stream) {
557
- that.emit(packet.type, packet.data, packet.stream, respond, null);
558
- } else {
559
- that.emit(packet.type, packet.data, respond, null);
560
- }
561
- }
562
- });
563
-
564
- /**
565
- * Create a namespace and inform the server
566
- *
567
- * @author Jelle De Loecker <jelle@develry.be>
568
- * @since 0.2.0
569
- * @version 0.2.0
570
- *
571
- * @param {String} type The typename of link to create
572
- * @param {Object} data The initial data to submit
573
- * @param {Function} cb Called when link isready
574
- *
575
- * @return {Linkup}
576
- */
577
- Client.setMethod(function linkup(type, data, cb) {
578
-
579
- var link;
580
-
581
- if (typeof data == 'function') {
582
- cb = data;
583
- data = {};
584
- }
585
-
586
- link = new Linkup(this, type, data);
587
-
588
- if (cb) {
589
- link.once('ready', function whenReady() {
590
- cb.call(link, link);
591
- });
592
- }
593
-
594
- return link;
595
- });
596
-
597
- /**
598
- * Listen for specific server-initiated linkups
599
- *
600
- * @author Jelle De Loecker <jelle@develry.be>
601
- * @since 0.2.0
602
- * @version 0.2.0
603
- *
604
- * @param {String} type The typename of link to listen to
605
- * @param {Function} callback
606
- */
607
- Client.setMethod(function onLinkup(type, callback) {
608
-
609
- if (this.server_linkup_listeners[type] == null) {
610
- this.server_linkup_listeners[type] = [];
611
- }
612
-
613
- this.server_linkup_listeners[type].push(callback);
1
+ /**
2
+ * See if the given object is a stream
3
+ *
4
+ * @author Jelle De Loecker <jelle@develry.be>
5
+ * @since 0.2.0
6
+ * @version 1.0.5
7
+ *
8
+ * @return {Boolean}
9
+ */
10
+ function isStream(obj) {
11
+ return obj && (typeof obj._read == 'function' || typeof obj._write == 'function') && typeof obj.on === 'function';
12
+ };
13
+
14
+ /**
15
+ * The Linkup class
16
+ *
17
+ * @author Jelle De Loecker <jelle@develry.be>
18
+ * @since 0.2.0
19
+ * @version 0.2.0
20
+ *
21
+ * @param {String} type
22
+ */
23
+ var Linkup = Blast.Collection.Function.inherits('Informer', function ClientLinkup(client, type, data) {
24
+
25
+ var server_object,
26
+ id;
27
+
28
+ if (type && typeof type == 'object' && type.id && data == null) {
29
+ server_object = type;
30
+ id = server_object.id;
31
+ type = server_object.type;
32
+ data = server_object.data;
33
+ } else {
34
+ id = type + '-' + Blast.Classes.Crypto.pseudoHex();
35
+ }
36
+
37
+ // The identifier
38
+ this.id = id;
39
+
40
+ // The typename of the link
41
+ this.type = type;
42
+
43
+ // The initial submitted data
44
+ this.initialData = data;
45
+
46
+ // Make the linkup store itself
47
+ client.linkups[this.id] = this;
48
+
49
+ // The parent server
50
+ this.client = client;
51
+
52
+ if (server_object) {
53
+ this.submit('ready');
54
+ } else {
55
+ // Establish the link
56
+ client._submit('linkup', {type: type, id: this.id, data: data});
57
+ }
58
+ });
59
+
60
+ /**
61
+ * Submit a message to the server on this link
62
+ *
63
+ * @author Jelle De Loecker <jelle@develry.be>
64
+ * @since 0.2.0
65
+ * @version 0.2.0
66
+ *
67
+ * @param {String} type
68
+ * @param {Object} data
69
+ * @param {Function} callback
70
+ */
71
+ Linkup.setMethod(function submit(type, data, stream, callback) {
72
+ this.client.submit([this.id, type], data, stream, callback);
73
+ });
74
+
75
+ /**
76
+ * Submit a message to the server on this link and return a promise
77
+ *
78
+ * @author Jelle De Loecker <jelle@elevenways.be>
79
+ * @since 1.1.2
80
+ * @version 1.1.2
81
+ *
82
+ * @param {String} type
83
+ * @param {Object} data
84
+ */
85
+ Linkup.setMethod(function demand(type, data, stream) {
86
+
87
+ const that = this;
88
+
89
+ let pledge = new Classes.Pledge(),
90
+ args = [type, data];
91
+
92
+ if (stream) {
93
+ args.push(stream);
94
+ }
95
+
96
+ this.submit(...args, function done(err, result) {
97
+
98
+ if (err) {
99
+ return pledge.reject(err);
100
+ }
101
+
102
+ pledge.resolve(result);
103
+ });
104
+
105
+ return pledge;
106
+ });
107
+
108
+ /**
109
+ * Create a stream
110
+ *
111
+ * @author Jelle De Loecker <jelle@develry.be>
112
+ * @since 0.2.0
113
+ * @version 0.2.0
114
+ */
115
+ Linkup.setMethod(function createStream() {
116
+ return this.client.createStream();
117
+ });
118
+
119
+ /**
120
+ * Destroy this linkup
121
+ *
122
+ * @author Jelle De Loecker <jelle@develry.be>
123
+ * @since 0.2.0
124
+ * @version 0.2.0
125
+ */
126
+ Linkup.setMethod(function destroy() {
127
+ delete this.client.linkups[this.id];
128
+ });
129
+
130
+ /**
131
+ * Actually make the socket.io connection,
132
+ * this requires the socket.io js to be loaded
133
+ *
134
+ * @author Jelle De Loecker <jelle@develry.be>
135
+ * @since 0.0.1
136
+ * @version 0.2.0
137
+ *
138
+ * @param {String} address Address to connect to
139
+ * @param {Object} data Announcement data
140
+ * @param {Function} callback
141
+ */
142
+ var Client = Blast.Collection.Function.inherits('Informer', function ClientSocket() {
143
+
144
+ var that = this;
145
+
146
+ // Connected is false
147
+ this.connected = false;
148
+
149
+ // The packet queue
150
+ this.queue = [];
151
+
152
+ // The callbacks
153
+ this.callbacks = {};
154
+
155
+ // Established linkups
156
+ this.linkups = {};
157
+
158
+ // The client message counter
159
+ this.counter = 0;
160
+
161
+ // The server object
162
+ this.server = null;
163
+
164
+ // The server stream
165
+ this.serverstream = null;
166
+
167
+ // The timesync offset
168
+ this.offset = 0;
169
+
170
+ // The connection latency
171
+ this.latency = 0;
172
+
173
+ // Enable auto reconnect
174
+ this.reconnect = true;
175
+
176
+ // Server-linkup listeners
177
+ this.server_linkup_listeners = {};
178
+
179
+ this.emitPacket = function emitPacket(packet) {
180
+
181
+ var stream;
182
+
183
+ if (packet.stream) {
184
+ stream = packet.stream;
185
+ delete packet.stream;
186
+ that.serverstream.emit('payload', stream, packet);
187
+ } else {
188
+ that.server.emit('payload', packet);
189
+ }
190
+ };
191
+ });
192
+
193
+ /**
194
+ * Get an offset-corrected timestamp
195
+ *
196
+ * @author Jelle De Loecker <jelle@develry.be>
197
+ * @since 0.2.1
198
+ * @version 0.2.1
199
+ *
200
+ * @return {Number}
201
+ */
202
+ Client.setMethod(function now() {
203
+ return Date.now() + (this.offset || 0);
204
+ });
205
+
206
+ /**
207
+ * Low level socket emit
208
+ *
209
+ * @author Jelle De Loecker <jelle@develry.be>
210
+ * @since 0.2.0
211
+ * @version 1.1.0
212
+ */
213
+ Client.setMethod(function _submit() {
214
+ var that = this,
215
+ args = Array.cast(arguments);
216
+
217
+ this.afterOnce('connected', function connected() {
218
+ that.server.emit.apply(that.server, args);
219
+ });
220
+ });
221
+
222
+ /**
223
+ * Submit method
224
+ *
225
+ * @author Jelle De Loecker <jelle@develry.be>
226
+ * @since 0.2.0
227
+ * @version 0.2.0
228
+ *
229
+ * @param {String} type
230
+ * @param {Object} data
231
+ * @param {IOStream} stream
232
+ * @param {Function} callback
233
+ */
234
+ Client.setMethod(function submit(type, data, stream, callback) {
235
+
236
+ var packet = {},
237
+ regular_stream;
238
+
239
+ if (isStream(data)) {
240
+ callback = stream;
241
+ stream = data;
242
+ data = undefined;
243
+ } else if (typeof data === 'function') {
244
+ callback = data;
245
+ data = undefined;
246
+ }
247
+
248
+ if (!stream || typeof stream == 'function') {
249
+ callback = stream;
250
+ stream = undefined;
251
+ } else if (stream && stream.constructor.name != 'IOStream') {
252
+ // Keep the regular stream
253
+ regular_stream = stream;
254
+
255
+ // Create an IOStream
256
+ stream = this.createStream();
257
+
258
+ // Pipe the regular stream into the IOStream
259
+ regular_stream.pipe(stream);
260
+ }
261
+
262
+ if (Array.isArray(type)) {
263
+ packet.link = type[0];
264
+ packet.type = type[1];
265
+ } else {
266
+ packet.type = type;
267
+ }
268
+
269
+ if (data && data.constructor.name == 'IOStream') {
270
+ stream = data;
271
+ packet.noData = true;
272
+ } else {
273
+ packet.data = data;
274
+ }
275
+
276
+ packet.id = 'c' + (++this.counter);
277
+ packet.stream = stream;
278
+
279
+ if (typeof callback == 'function') {
280
+ this.callbacks[packet.id] = callback;
281
+ packet.respond = true;
282
+ }
283
+
284
+ if (this.connected) {
285
+ this.emitPacket(packet);
286
+ } else {
287
+ this.queue.push(packet);
288
+ }
289
+ });
290
+
291
+ /**
292
+ * Create a stream we can send through a websocket connection
293
+ *
294
+ * @author Jelle De Loecker <jelle@develry.be>
295
+ * @since 0.2.0
296
+ * @version 0.2.0
297
+ */
298
+ Client.setMethod(function createStream() {
299
+
300
+ var stream;
301
+
302
+ if (Blast.isNode) {
303
+ stream = alchemy.use('socket.io-stream').createStream();
304
+ } else {
305
+ stream = ss.createStream();
306
+ }
307
+
308
+ return stream;
309
+ });
310
+
311
+ /**
312
+ * Make the actual connection
313
+ *
314
+ * @author Jelle De Loecker <jelle@develry.be>
315
+ * @since 0.2.0
316
+ * @version 1.0.5
317
+ *
318
+ * @param {Function} callback
319
+ */
320
+ Client.setMethod(function connect(address, data, callback) {
321
+
322
+ var that = this,
323
+ ioServer,
324
+ serverstream,
325
+ config = {},
326
+ server;
327
+
328
+ if (typeof address == 'function') {
329
+ callback = address;
330
+ data = {};
331
+ address = null;
332
+ } else if (typeof data == 'function') {
333
+ callback = data;
334
+ data = {};
335
+ }
336
+
337
+ if (!data || typeof data != 'object') {
338
+ data = {};
339
+ }
340
+
341
+ if (typeof io != 'undefined') {
342
+ ioServer = io;
343
+ } else if (typeof alchemy != 'undefined') {
344
+ ioServer = alchemy.use('socket.io-client');
345
+ } else {
346
+ return callback(new Error('Could not find socket.io client library'));
347
+ }
348
+
349
+ // The address to connect to
350
+ this.address = address;
351
+
352
+ if (Blast.isNode) {
353
+ data.connection_type = 'node';
354
+ data.discovery = alchemy.discovery_id;
355
+ } else {
356
+ data.connection_type = 'browser';
357
+ data.scene = hawkejs.scene.sceneId;
358
+ data.last_update = alchemy.last_update;
359
+ }
360
+
361
+ if (!this.reconnect) {
362
+ config.reconnection = false;
363
+ }
364
+
365
+ // Create the connection to the server
366
+ if (address) {
367
+ server = ioServer.connect(address, config);
368
+ } else {
369
+ server = ioServer.connect(config);
370
+ }
371
+
372
+ if (Blast.isNode) {
373
+ serverstream = alchemy.use('socket.io-stream')(server);
374
+ } else {
375
+ serverstream = ss(server);
376
+ }
377
+
378
+ this.server = server;
379
+ this.serverstream = serverstream;
380
+
381
+ // Announce ourselves when we've connected
382
+ server.on('connect', function onConnect() {
383
+ server.emit('announce', data);
384
+ });
385
+
386
+ // Emit the close event once we get disconnected from the server
387
+ server.on('disconnect', function closed() {
388
+ that.connected = false;
389
+ that.emit('close');
390
+ });
391
+
392
+ // Listen to timesync commands
393
+ server.on('timesync', function gotTimesync(data) {
394
+
395
+ // When offset is not defined,
396
+ // the server is actually requesting our timestamp
397
+ if (data.offset == null) {
398
+ data.client_time = Date.now();
399
+ server.emit('timesync', data);
400
+ return;
401
+ }
402
+
403
+ // If the offset property is set,
404
+ // the timesync procedure has finished
405
+
406
+ // Set the values in this object
407
+ that.offset = data.offset || 0;
408
+ that.latency = data.latency || 0;
409
+
410
+ // Emit it as an event, too
411
+ that.emit('timesynced', that.offset, that.latency);
412
+ });
413
+
414
+ // Listen for the ready event
415
+ server.on('ready', function onReady() {
416
+
417
+ that.connected = true;
418
+
419
+ if (callback) {
420
+ callback();
421
+ }
422
+
423
+ that.emit('connected');
424
+
425
+ // Emit all the queued packets
426
+ that.queue.forEach(that.emitPacket);
427
+
428
+ // Request a timesync
429
+ that.server.emit('timesync', {start: Date.now()});
430
+
431
+ // Reset the queue
432
+ that.queue.length = 0;
433
+ });
434
+
435
+ server.on('error', function(err) {
436
+ console.log('Socket error:', err);
437
+ });
438
+
439
+ // Listen for cookies
440
+ server.on('alchemy-set-cookie', function setCookie(data) {
441
+ if (Blast.isNode) {
442
+ // @TODO: set node cookie?
443
+ } else {
444
+ hawkejs.scene.cookie(data.name, data.value, data.options);
445
+ }
446
+ });
447
+
448
+ // Listen for server initiated linkups
449
+ server.on('linkup', function gotLinkup(config) {
450
+
451
+ var linkup = new Linkup(that, config),
452
+ i;
453
+
454
+ // Look through the server-linkup callbacks
455
+ if (that.server_linkup_listeners[config.type]) {
456
+ for (i = 0; i < that.server_linkup_listeners[config.type].length; i++) {
457
+ that.server_linkup_listeners[config.type][i].call(that, linkup, config.data);
458
+ }
459
+ }
460
+
461
+ // Emit it on the client itself
462
+ that.emit(linkup.type, linkup, null);
463
+ });
464
+
465
+ // Listen for payloads
466
+ server.on('payload', onPacket);
467
+
468
+ // Listen for payloads with streams
469
+ serverstream.on('payload', function onPayload(stream, packet) {
470
+ packet.stream = stream;
471
+ onPacket(packet);
472
+ });
473
+
474
+ // Listen for responses with streams
475
+ serverstream.on('response', function onStreamingResponse(stream, packet) {
476
+ packet.stream = stream;
477
+ onResponse(packet);
478
+ });
479
+
480
+ // Listen for responses
481
+ server.on('response', onResponse);
482
+
483
+ // The function that handles responses
484
+ function onResponse(packet) {
485
+
486
+ if (typeof that.callbacks[packet.respond_to] === 'function') {
487
+
488
+ if (packet.err && typeof packet.err == 'object') {
489
+ try {
490
+ packet.err = JSON.undry(packet.err);
491
+ } catch (err) {
492
+ console.log('Error undrying error:', err, packet);
493
+ }
494
+ }
495
+
496
+ try {
497
+ if (packet.data && typeof packet.data == 'object') {
498
+ packet.data = JSON.undry(packet.data);
499
+ }
500
+ } catch (err) {
501
+ console.log('ERROR UNDRYING PACKET:', err, packet);
502
+ return;
503
+ }
504
+
505
+ if (packet.noData) {
506
+ that.callbacks[packet.respond_to](packet.err, packet.stream);
507
+ } else if (packet.stream) {
508
+ that.callbacks[packet.respond_to](packet.err, packet.data, packet.stream);
509
+ } else {
510
+ that.callbacks[packet.respond_to](packet.err, packet.data);
511
+ }
512
+ }
513
+
514
+ delete that.callbacks[packet.respond_to];
515
+ }
516
+
517
+ // The function that handles packets
518
+ function onPacket(packet) {
519
+
520
+ var respond;
521
+
522
+ try {
523
+ if (packet.data && typeof packet.data == 'object') {
524
+ packet.data = JSON.undry(packet.data);
525
+ }
526
+ } catch (err) {
527
+ console.log('ERROR UNDRYING PACKET:', err, packet);
528
+ return;
529
+ }
530
+
531
+ if (packet.respond) {
532
+ respond = function respond(err, data) {
533
+ var responsePacket = {};
534
+
535
+ responsePacket.err = err;
536
+ responsePacket.respond_to = packet.id;
537
+ responsePacket.data = data;
538
+
539
+ server.emit('response', responsePacket);
540
+ };
541
+ }
542
+
543
+ // See if this is for a specific linkup
544
+ if (packet.link) {
545
+ if (that.linkups[packet.link]) {
546
+ if (packet.stream) {
547
+ that.linkups[packet.link].emit(packet.type, packet.data, packet.stream, respond, null);
548
+ } else {
549
+ that.linkups[packet.link].emit(packet.type, packet.data, respond, null);
550
+ }
551
+ }
552
+
553
+ return;
554
+ }
555
+
556
+ if (packet.stream) {
557
+ that.emit(packet.type, packet.data, packet.stream, respond, null);
558
+ } else {
559
+ that.emit(packet.type, packet.data, respond, null);
560
+ }
561
+ }
562
+ });
563
+
564
+ /**
565
+ * Create a namespace and inform the server
566
+ *
567
+ * @author Jelle De Loecker <jelle@develry.be>
568
+ * @since 0.2.0
569
+ * @version 0.2.0
570
+ *
571
+ * @param {String} type The typename of link to create
572
+ * @param {Object} data The initial data to submit
573
+ * @param {Function} cb Called when link isready
574
+ *
575
+ * @return {Linkup}
576
+ */
577
+ Client.setMethod(function linkup(type, data, cb) {
578
+
579
+ var link;
580
+
581
+ if (typeof data == 'function') {
582
+ cb = data;
583
+ data = {};
584
+ }
585
+
586
+ link = new Linkup(this, type, data);
587
+
588
+ if (cb) {
589
+ link.once('ready', function whenReady() {
590
+ cb.call(link, link);
591
+ });
592
+ }
593
+
594
+ return link;
595
+ });
596
+
597
+ /**
598
+ * Listen for specific server-initiated linkups
599
+ *
600
+ * @author Jelle De Loecker <jelle@develry.be>
601
+ * @since 0.2.0
602
+ * @version 0.2.0
603
+ *
604
+ * @param {String} type The typename of link to listen to
605
+ * @param {Function} callback
606
+ */
607
+ Client.setMethod(function onLinkup(type, callback) {
608
+
609
+ if (this.server_linkup_listeners[type] == null) {
610
+ this.server_linkup_listeners[type] = [];
611
+ }
612
+
613
+ this.server_linkup_listeners[type].push(callback);
614
614
  });