UNPKG

4.64 kBJavaScriptView Raw
1var Emitter = require('events');
2var URL = require('url');
3
4module.exports = createClient;
5
6var 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
21var 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
32function 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
48function 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
58var slice = [].slice;
59function 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 // callback specific
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 // TODO handle host changing here
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
174function noop() {}