1 | var Emitter = require('events');
|
2 | var URL = require('url');
|
3 |
|
4 | module.exports = createClient;
|
5 |
|
6 | var events = [
|
7 | 'mounted',
|
8 | 'unmounted',
|
9 | 'notFound',
|
10 | 'authenticationRequired',
|
11 | 'authenticationInvalid',
|
12 | 'unauthorized',
|
13 | 'authenticationAcknowledged',
|
14 | 'actionAcknowledged',
|
15 | 'actionInvalid',
|
16 | 'error',
|
17 | 'info',
|
18 | 'call'
|
19 | ];
|
20 |
|
21 | var handler = events.reduce(function(acc, name) {
|
22 | acc[name] = function(message) {
|
23 | this.emitter.emit(message.instance, name, message);
|
24 | };
|
25 | return acc;
|
26 | }, {
|
27 | init: function(emitter) {
|
28 | this.emitter = emitter;
|
29 | }
|
30 | });
|
31 |
|
32 | function createClient(createConnection) {
|
33 | var hosts = {};
|
34 | return function createInstance(src, props, app) {
|
35 | var url = URL.parse(src);
|
36 | var path = url.pathname;
|
37 | url.pathname = url.path = '';
|
38 |
|
39 | url = URL.format(url);
|
40 | var host = hosts[url];
|
41 |
|
42 | if (!host) host = hosts[url] = createHost(url, createConnection);
|
43 |
|
44 | return host(path, props, app);
|
45 | };
|
46 | }
|
47 |
|
48 | function createHost(host, createConnection) {
|
49 | var emitter = new Emitter();
|
50 | var conn = createConnection(host, handler, emitter);
|
51 |
|
52 | var i = 0;
|
53 | return function(path, props, app) {
|
54 | return createInstance(conn, emitter, i++, path, props, app);
|
55 | };
|
56 | }
|
57 |
|
58 | var slice = [].slice;
|
59 | function createInstance(conn, emitter, instance, path, props, app) {
|
60 | var actions = {};
|
61 | var authentications = {};
|
62 |
|
63 | function call(name) {
|
64 | if (app && app[name]) app[name].apply(app, slice.call(arguments, 1));
|
65 | }
|
66 |
|
67 | function invokeCallback(callbacks, id, error, message) {
|
68 | if (typeof callbacks[id] == 'function') callbacks[id](error, message);
|
69 | delete callbacks[id];
|
70 | }
|
71 |
|
72 | var currentState = null;
|
73 |
|
74 | var events = {
|
75 | mounted: function(_, message) {
|
76 | currentState = message.state;
|
77 | client.isMounted = true;
|
78 | call('mount', message);
|
79 | },
|
80 | unmounted: function(_, message) {
|
81 | client.isMounted = false;
|
82 | call('unmount', message);
|
83 | },
|
84 | notFound: function(_, message) {
|
85 | call('notFound', message);
|
86 | },
|
87 | authenticationRequired: function(_, message) {
|
88 | call('authenticationRequired', message);
|
89 | },
|
90 | unauthorized: function(_, message) {
|
91 | call('unauthorized', message);
|
92 | },
|
93 |
|
94 | info: function(_, message) {
|
95 | call('info', message);
|
96 | },
|
97 | call: function(_, message) {
|
98 | call('call', message, function(err, data) {
|
99 | var ref = message.ref;
|
100 | err ?
|
101 | conn.callError(instance, ref, '' + err) :
|
102 | conn.callResponse(instance, ref, data);
|
103 | });
|
104 | },
|
105 |
|
106 | error: function(_, message) {
|
107 | call('error', message);
|
108 | },
|
109 |
|
110 |
|
111 | authenticationInvalid: function(_, message) {
|
112 | invokeCallback(authentications, message.method, new Error(message.info), message);
|
113 | },
|
114 | authenticationAcknowledged: function(_, message) {
|
115 | invokeCallback(authentications, message.method, null, message);
|
116 | },
|
117 |
|
118 | actionInvalid: function(_, message) {
|
119 | invokeCallback(actions, message.ref, new Error(message.info), message);
|
120 | },
|
121 | actionAcknowledged: function(_, message) {
|
122 | invokeCallback(actions, message.ref, null, message);
|
123 | }
|
124 | };
|
125 |
|
126 | function onEvent(event) {
|
127 | (events[event] || noop).apply(null, arguments);
|
128 | }
|
129 |
|
130 | emitter.on(instance, onEvent);
|
131 |
|
132 | if (path) conn.mount(instance, path, currentState, props);
|
133 |
|
134 | var client = {
|
135 | instance: instance,
|
136 | isMounted: false,
|
137 | mount: function(src, newProps) {
|
138 | if (!app) return client;
|
139 | var url = URL.parse(src);
|
140 |
|
141 | path = url.pathname;
|
142 | props = newProps;
|
143 | conn.mount(instance, path, currentState, props);
|
144 | return client;
|
145 | },
|
146 | unmount: function() {
|
147 | if (!app) return client;
|
148 | app = null;
|
149 | emitter.removeListener(instance, onEvent);
|
150 | conn.unmount(instance);
|
151 | return client;
|
152 | },
|
153 | action: function(ref, data, cb) {
|
154 | if (!app) return client;
|
155 | if (actions[ref]) return cb(new Error('Another action for ' + JSON.stringify(ref) + ' in currently being executed'));
|
156 | if (!conn.isConnected()) conn.mount(instance, path, currentState, props);
|
157 | conn.action(instance, ref, data);
|
158 | actions[ref] = cb || true;
|
159 | return client;
|
160 | },
|
161 | authenticate: function(method, token, cb) {
|
162 | if (!app) return null;
|
163 | if (authentications[method]) return cb(new Error('Another authentication for ' + JSON.stringify(method) + ' in currently being executed'));
|
164 | if (!conn.isConnected()) conn.mount(instance, path, currentState, props);
|
165 | conn.authenticate(instance, method, token);
|
166 | authentications[method] = cb || true;
|
167 | return client;
|
168 | }
|
169 | };
|
170 |
|
171 | return client;
|
172 | }
|
173 |
|
174 | function noop() {}
|