opal-phoenix 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 5c8e816807227235a03427974e5205d45b308506
4
+ data.tar.gz: f40090afb5ad69d3326f20ee739d35df46847291
5
+ SHA512:
6
+ metadata.gz: 343cf19d645d00c99c815d01061b39215fca77178a9bce461d29de30e69e74ae4e1f5c4b2b9c8f473e5f82b3f3dd95249dd944c7fa3bb9a5da481132bfcb8a5b
7
+ data.tar.gz: 416f0925c66af852d372b1d019906137b4721de5bb255cfcfc95df8ea5a4cbbd4faf40d1cb4bddb540828d7eedc117c211a65a15e33e221aad13bfd15a88d2ba
data/.gitignore ADDED
@@ -0,0 +1,3 @@
1
+ pkg/
2
+ *.gem
3
+ Gemfile.lock
data/Gemfile ADDED
@@ -0,0 +1,2 @@
1
+ source "https://rubygems.org"
2
+ gemspec
data/README.md ADDED
@@ -0,0 +1,65 @@
1
+ # opal-phoenix
2
+
3
+ Opal wrapper for [Phoenix Framework](http://phoenixframework.org) javascript library.
4
+
5
+ ## usage
6
+
7
+ ### Server side
8
+ config.ru, Rakefile, Rails, Sinatra, etc.
9
+
10
+ ```ruby
11
+ require 'opal-phoenix'
12
+ ```
13
+
14
+ Gemfile
15
+
16
+ ```ruby
17
+ gem 'opal-phoenix'
18
+ ```
19
+
20
+ ### Browser side
21
+
22
+ ```ruby
23
+ require 'phoenix'
24
+
25
+ # setup socket
26
+ socket = Phoenix::Socket.new('ws://localhost:4000/ws')
27
+
28
+ socket.on_error do
29
+ $console.log 'socket error!'
30
+ end
31
+
32
+ socket.on_close do
33
+ $console.log 'socket closed!'
34
+ end
35
+
36
+ # connect to socket
37
+ socket.connect
38
+
39
+ # setup channel
40
+ channel = socket.channel('lobby', params: {asd: 'xcvxcv'})
41
+
42
+ channel.on_error do
43
+ $console.log 'channel error!'
44
+ end
45
+
46
+ channel.on_close do
47
+ $console.log 'channel closed!'
48
+ end
49
+
50
+ channel.on 'msg' do |payload|
51
+ $console.log "payload: #{payload}"
52
+ end
53
+
54
+ # join channel
55
+ channel
56
+ .join
57
+ .receive('ok') { $console.log 'ok' }
58
+ .receive('failed') { $console.log 'failed' }
59
+
60
+ # push a message
61
+ channel
62
+ .push("msg", {a: :b})
63
+ .receive('ok') { $console.log 'ok' }
64
+ .receive('failed') { $console.log 'failed' }
65
+ ```
data/Rakefile ADDED
@@ -0,0 +1,14 @@
1
+ require 'bundler'
2
+ Bundler.require
3
+ Bundler::GemHelper.install_tasks
4
+
5
+ require 'open-uri'
6
+
7
+ desc 'Update Phoenix JS lib'
8
+ task :update_js do
9
+ js_lib_url = 'https://raw.githubusercontent.com/phoenixframework/phoenix/master/priv/static/phoenix.js'
10
+ js_lib_dest = File.join(File.dirname(__FILE__), './lib/phoenix_js.js')
11
+ open(js_lib_url) do |f|
12
+ File.write(js_lib_dest, f.readlines.join)
13
+ end
14
+ end
@@ -0,0 +1,3 @@
1
+ require 'opal'
2
+
3
+ Opal.append_path File.expand_path('../../opal', __FILE__)
@@ -0,0 +1,17 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = 'opal-phoenix'
3
+ s.version = '0.0.1'
4
+ s.authors = ['Michał Kalbarczyk']
5
+ s.email = 'fazibear@gmail.com'
6
+ s.homepage = 'http://github.com/fazibear/opal-phoenix'
7
+ s.summary = 'Phoenix client wrapper for opal'
8
+ s.description = 'Phoenix client wrapper for opal'
9
+
10
+ s.files = `git ls-files`.split("\n")
11
+ s.executables = `git ls-files -- bin/*`.split("\n").map { |f| File.basename(f) }
12
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
13
+ s.require_paths = ['lib']
14
+
15
+ s.add_dependency 'opal'
16
+ s.add_development_dependency 'rake'
17
+ end
@@ -0,0 +1 @@
1
+ require 'phoenix'
data/opal/phoenix.rb ADDED
@@ -0,0 +1,4 @@
1
+ require 'phoenix_js'
2
+ require 'phoenix/channel'
3
+ require 'phoenix/socket'
4
+ require 'phoenix/push'
@@ -0,0 +1,37 @@
1
+ module Phoenix
2
+ class Channel
3
+ include Native
4
+
5
+ def initialize(topic, params, this)
6
+ super(`new Phoenix.Channel(#{topic}, #{params.to_n}, #{this})`)
7
+ end
8
+
9
+ def on(msg, &block)
10
+ `#{@native}.on(#{msg}, #{block})`
11
+ end
12
+
13
+ def push(msg, payload)
14
+ Push.new `#{@native}.push(#{msg}, #{payload})`
15
+ end
16
+
17
+ def join
18
+ Push.new `#{@native}.join()`
19
+ end
20
+
21
+ def leave
22
+ Push.new `#{@native}.leave()`
23
+ end
24
+
25
+ def joined?
26
+ `#{@native}.joinedOnce`
27
+ end
28
+
29
+ def on_error(&block)
30
+ `#{@native}.onError(#{block})`
31
+ end
32
+
33
+ def on_close(&block)
34
+ `#{@native}.onClose(#{block})`
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,13 @@
1
+ module Phoenix
2
+ class Push
3
+ include Native
4
+
5
+ def initialize(push)
6
+ super(push)
7
+ end
8
+
9
+ def receive(msg, &block)
10
+ Push.new `#{@native}.receive(#{msg}, #{block})`
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,25 @@
1
+ module Phoenix
2
+ class Socket
3
+ include Native
4
+
5
+ alias_native :connect
6
+
7
+ def initialize(url, params = {})
8
+ super(`new Phoenix.Socket(#{url.to_s}, #{params.to_n})`)
9
+ end
10
+
11
+ def channel(topic, *params)
12
+ chan = Channel.new(topic, params, @native)
13
+ `#{@native}.channels.push(#{chan.to_n})`
14
+ chan
15
+ end
16
+
17
+ def on_error(&block)
18
+ `#{@native}.onError(#{block})`
19
+ end
20
+
21
+ def on_close(&block)
22
+ `#{@native}.onClose(#{block})`
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,1065 @@
1
+ (function() {
2
+ 'use strict';
3
+
4
+ var globals = typeof window === 'undefined' ? global : window;
5
+ if (typeof globals.require === 'function') return;
6
+
7
+ var modules = {};
8
+ var cache = {};
9
+ var has = ({}).hasOwnProperty;
10
+
11
+ var aliases = {};
12
+
13
+ var endsWith = function(str, suffix) {
14
+ return str.indexOf(suffix, str.length - suffix.length) !== -1;
15
+ };
16
+
17
+ var unalias = function(alias, loaderPath) {
18
+ var start = 0;
19
+ if (loaderPath) {
20
+ if (loaderPath.indexOf('components/' === 0)) {
21
+ start = 'components/'.length;
22
+ }
23
+ if (loaderPath.indexOf('/', start) > 0) {
24
+ loaderPath = loaderPath.substring(start, loaderPath.indexOf('/', start));
25
+ }
26
+ }
27
+ var result = aliases[alias + '/index.js'] || aliases[loaderPath + '/deps/' + alias + '/index.js'];
28
+ if (result) {
29
+ return 'components/' + result.substring(0, result.length - '.js'.length);
30
+ }
31
+ return alias;
32
+ };
33
+
34
+ var expand = (function() {
35
+ var reg = /^\.\.?(\/|$)/;
36
+ return function(root, name) {
37
+ var results = [], parts, part;
38
+ parts = (reg.test(name) ? root + '/' + name : name).split('/');
39
+ for (var i = 0, length = parts.length; i < length; i++) {
40
+ part = parts[i];
41
+ if (part === '..') {
42
+ results.pop();
43
+ } else if (part !== '.' && part !== '') {
44
+ results.push(part);
45
+ }
46
+ }
47
+ return results.join('/');
48
+ };
49
+ })();
50
+ var dirname = function(path) {
51
+ return path.split('/').slice(0, -1).join('/');
52
+ };
53
+
54
+ var localRequire = function(path) {
55
+ return function(name) {
56
+ var absolute = expand(dirname(path), name);
57
+ return globals.require(absolute, path);
58
+ };
59
+ };
60
+
61
+ var initModule = function(name, definition) {
62
+ var module = {id: name, exports: {}};
63
+ cache[name] = module;
64
+ definition(module.exports, localRequire(name), module);
65
+ return module.exports;
66
+ };
67
+
68
+ var require = function(name, loaderPath) {
69
+ var path = expand(name, '.');
70
+ if (loaderPath == null) loaderPath = '/';
71
+ path = unalias(name, loaderPath);
72
+
73
+ if (has.call(cache, path)) return cache[path].exports;
74
+ if (has.call(modules, path)) return initModule(path, modules[path]);
75
+
76
+ var dirIndex = expand(path, './index');
77
+ if (has.call(cache, dirIndex)) return cache[dirIndex].exports;
78
+ if (has.call(modules, dirIndex)) return initModule(dirIndex, modules[dirIndex]);
79
+
80
+ throw new Error('Cannot find module "' + name + '" from '+ '"' + loaderPath + '"');
81
+ };
82
+
83
+ require.alias = function(from, to) {
84
+ aliases[to] = from;
85
+ };
86
+
87
+ require.register = require.define = function(bundle, fn) {
88
+ if (typeof bundle === 'object') {
89
+ for (var key in bundle) {
90
+ if (has.call(bundle, key)) {
91
+ modules[key] = bundle[key];
92
+ }
93
+ }
94
+ } else {
95
+ modules[bundle] = fn;
96
+ }
97
+ };
98
+
99
+ require.list = function() {
100
+ var result = [];
101
+ for (var item in modules) {
102
+ if (has.call(modules, item)) {
103
+ result.push(item);
104
+ }
105
+ }
106
+ return result;
107
+ };
108
+
109
+ require.brunch = true;
110
+ globals.require = require;
111
+ })();
112
+ require.define({'phoenix': function(exports, require, module){ // Phoenix Channels JavaScript client
113
+ //
114
+ // ## Socket Connection
115
+ //
116
+ // A single connection is established to the server and
117
+ // channels are mulitplexed over the connection.
118
+ // Connect to the server using the `Socket` class:
119
+ //
120
+ // let socket = new Socket("/ws", {params: {userToken: "123"}})
121
+ // socket.connect()
122
+ //
123
+ // The `Socket` constructor takes the mount point of the socket,
124
+ // the authentication params, as well as options that can be found in
125
+ // the Socket docs, such as configuring the `LongPoll` transport, and
126
+ // heartbeat.
127
+ //
128
+ // ## Channels
129
+ //
130
+ // Channels are isolated, concurrent processes on the server that
131
+ // subscribe to topics and broker events between the client and server.
132
+ // To join a channel, you must provide the topic, and channel params for
133
+ // authorization. Here's an example chat room example where `"new_msg"`
134
+ // events are listened for, messages are pushed to the server, and
135
+ // the channel is joined with ok/error/timeout matches:
136
+ //
137
+ // let channel = socket.channel("rooms:123", {token: roomToken})
138
+ // channel.on("new_msg", msg => console.log("Got message", msg) )
139
+ // $input.onEnter( e => {
140
+ // channel.push("new_msg", {body: e.target.val}, 10000)
141
+ // .receive("ok", (msg) => console.log("created message", msg) )
142
+ // .receive("error", (reasons) => console.log("create failed", reasons) )
143
+ // .receive("timeout", () => console.log("Networking issue...") )
144
+ // })
145
+ // channel.join()
146
+ // .receive("ok", ({messages}) => console.log("catching up", messages) )
147
+ // .receive("error", ({reason}) => console.log("failed join", reason) )
148
+ // .receive("timeout", () => console.log("Networking issue. Still waiting...") )
149
+ //
150
+ //
151
+ // ## Joining
152
+ //
153
+ // Joining a channel with `channel.join(topic, params)`, binds the params to
154
+ // `channel.params`. Subsequent rejoins will send up the modified params for
155
+ // updating authorization params, or passing up last_message_id information.
156
+ // Successful joins receive an "ok" status, while unsuccessful joins
157
+ // receive "error".
158
+ //
159
+ //
160
+ // ## Pushing Messages
161
+ //
162
+ // From the previous example, we can see that pushing messages to the server
163
+ // can be done with `channel.push(eventName, payload)` and we can optionally
164
+ // receive responses from the push. Additionally, we can use
165
+ // `receive("timeout", callback)` to abort waiting for our other `receive` hooks
166
+ // and take action after some period of waiting.
167
+ //
168
+ //
169
+ // ## Socket Hooks
170
+ //
171
+ // Lifecycle events of the multiplexed connection can be hooked into via
172
+ // `socket.onError()` and `socket.onClose()` events, ie:
173
+ //
174
+ // socket.onError( () => console.log("there was an error with the connection!") )
175
+ // socket.onClose( () => console.log("the connection dropped") )
176
+ //
177
+ //
178
+ // ## Channel Hooks
179
+ //
180
+ // For each joined channel, you can bind to `onError` and `onClose` events
181
+ // to monitor the channel lifecycle, ie:
182
+ //
183
+ // channel.onError( () => console.log("there was an error!") )
184
+ // channel.onClose( () => console.log("the channel has gone away gracefully") )
185
+ //
186
+ // ### onError hooks
187
+ //
188
+ // `onError` hooks are invoked if the socket connection drops, or the channel
189
+ // crashes on the server. In either case, a channel rejoin is attemtped
190
+ // automatically in an exponential backoff manner.
191
+ //
192
+ // ### onClose hooks
193
+ //
194
+ // `onClose` hooks are invoked only in two cases. 1) the channel explicitly
195
+ // closed on the server, or 2). The client explicitly closed, by calling
196
+ // `channel.leave()`
197
+ //
198
+
199
+ "use strict";
200
+
201
+ exports.__esModule = true;
202
+
203
+ function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
204
+
205
+ var VSN = "1.0.0";
206
+ var SOCKET_STATES = { connecting: 0, open: 1, closing: 2, closed: 3 };
207
+ var DEFAULT_TIMEOUT = 10000;
208
+ var CHANNEL_STATES = {
209
+ closed: "closed",
210
+ errored: "errored",
211
+ joined: "joined",
212
+ joining: "joining"
213
+ };
214
+ var CHANNEL_EVENTS = {
215
+ close: "phx_close",
216
+ error: "phx_error",
217
+ join: "phx_join",
218
+ reply: "phx_reply",
219
+ leave: "phx_leave"
220
+ };
221
+ var TRANSPORTS = {
222
+ longpoll: "longpoll",
223
+ websocket: "websocket"
224
+ };
225
+
226
+ var Push = (function () {
227
+
228
+ // Initializes the Push
229
+ //
230
+ // channel - The Channel
231
+ // event - The event, for example `"phx_join"`
232
+ // payload - The payload, for example `{user_id: 123}`
233
+ // timeout - The push timeout in milliseconds
234
+ //
235
+
236
+ function Push(channel, event, payload, timeout) {
237
+ _classCallCheck(this, Push);
238
+
239
+ this.channel = channel;
240
+ this.event = event;
241
+ this.payload = payload || {};
242
+ this.receivedResp = null;
243
+ this.timeout = timeout;
244
+ this.timeoutTimer = null;
245
+ this.recHooks = [];
246
+ this.sent = false;
247
+ }
248
+
249
+ Push.prototype.resend = function resend(timeout) {
250
+ this.timeout = timeout;
251
+ this.cancelRefEvent();
252
+ this.ref = null;
253
+ this.refEvent = null;
254
+ this.receivedResp = null;
255
+ this.sent = false;
256
+ this.send();
257
+ };
258
+
259
+ Push.prototype.send = function send() {
260
+ if (this.hasReceived("timeout")) {
261
+ return;
262
+ }
263
+ this.startTimeout();
264
+ this.sent = true;
265
+ this.channel.socket.push({
266
+ topic: this.channel.topic,
267
+ event: this.event,
268
+ payload: this.payload,
269
+ ref: this.ref
270
+ });
271
+ };
272
+
273
+ Push.prototype.receive = function receive(status, callback) {
274
+ if (this.hasReceived(status)) {
275
+ callback(this.receivedResp.response);
276
+ }
277
+
278
+ this.recHooks.push({ status: status, callback: callback });
279
+ return this;
280
+ };
281
+
282
+ // private
283
+
284
+ Push.prototype.matchReceive = function matchReceive(_ref) {
285
+ var status = _ref.status;
286
+ var response = _ref.response;
287
+ var ref = _ref.ref;
288
+
289
+ this.recHooks.filter(function (h) {
290
+ return h.status === status;
291
+ }).forEach(function (h) {
292
+ return h.callback(response);
293
+ });
294
+ };
295
+
296
+ Push.prototype.cancelRefEvent = function cancelRefEvent() {
297
+ if (!this.refEvent) {
298
+ return;
299
+ }
300
+ this.channel.off(this.refEvent);
301
+ };
302
+
303
+ Push.prototype.cancelTimeout = function cancelTimeout() {
304
+ clearTimeout(this.timeoutTimer);
305
+ this.timeoutTimer = null;
306
+ };
307
+
308
+ Push.prototype.startTimeout = function startTimeout() {
309
+ var _this = this;
310
+
311
+ if (this.timeoutTimer) {
312
+ return;
313
+ }
314
+ this.ref = this.channel.socket.makeRef();
315
+ this.refEvent = this.channel.replyEventName(this.ref);
316
+
317
+ this.channel.on(this.refEvent, function (payload) {
318
+ _this.cancelRefEvent();
319
+ _this.cancelTimeout();
320
+ _this.receivedResp = payload;
321
+ _this.matchReceive(payload);
322
+ });
323
+
324
+ this.timeoutTimer = setTimeout(function () {
325
+ _this.trigger("timeout", {});
326
+ }, this.timeout);
327
+ };
328
+
329
+ Push.prototype.hasReceived = function hasReceived(status) {
330
+ return this.receivedResp && this.receivedResp.status === status;
331
+ };
332
+
333
+ Push.prototype.trigger = function trigger(status, response) {
334
+ this.channel.trigger(this.refEvent, { status: status, response: response });
335
+ };
336
+
337
+ return Push;
338
+ })();
339
+
340
+ var Channel = (function () {
341
+ function Channel(topic, params, socket) {
342
+ var _this2 = this;
343
+
344
+ _classCallCheck(this, Channel);
345
+
346
+ this.state = CHANNEL_STATES.closed;
347
+ this.topic = topic;
348
+ this.params = params || {};
349
+ this.socket = socket;
350
+ this.bindings = [];
351
+ this.timeout = this.socket.timeout;
352
+ this.joinedOnce = false;
353
+ this.joinPush = new Push(this, CHANNEL_EVENTS.join, this.params, this.timeout);
354
+ this.pushBuffer = [];
355
+ this.rejoinTimer = new Timer(function () {
356
+ return _this2.rejoinUntilConnected();
357
+ }, this.socket.reconnectAfterMs);
358
+ this.joinPush.receive("ok", function () {
359
+ _this2.state = CHANNEL_STATES.joined;
360
+ _this2.rejoinTimer.reset();
361
+ _this2.pushBuffer.forEach(function (pushEvent) {
362
+ return pushEvent.send();
363
+ });
364
+ _this2.pushBuffer = [];
365
+ });
366
+ this.onClose(function () {
367
+ _this2.socket.log("channel", "close " + _this2.topic);
368
+ _this2.state = CHANNEL_STATES.closed;
369
+ _this2.socket.remove(_this2);
370
+ });
371
+ this.onError(function (reason) {
372
+ _this2.socket.log("channel", "error " + _this2.topic, reason);
373
+ _this2.state = CHANNEL_STATES.errored;
374
+ _this2.rejoinTimer.setTimeout();
375
+ });
376
+ this.joinPush.receive("timeout", function () {
377
+ if (_this2.state !== CHANNEL_STATES.joining) {
378
+ return;
379
+ }
380
+
381
+ _this2.socket.log("channel", "timeout " + _this2.topic, reason);
382
+ _this2.state = CHANNEL_STATES.errored;
383
+ _this2.rejoinTimer.setTimeout();
384
+ });
385
+ this.on(CHANNEL_EVENTS.reply, function (payload, ref) {
386
+ _this2.trigger(_this2.replyEventName(ref), payload);
387
+ });
388
+ }
389
+
390
+ Channel.prototype.rejoinUntilConnected = function rejoinUntilConnected() {
391
+ this.rejoinTimer.setTimeout();
392
+ if (this.socket.isConnected()) {
393
+ this.rejoin();
394
+ }
395
+ };
396
+
397
+ Channel.prototype.join = function join() {
398
+ var timeout = arguments.length <= 0 || arguments[0] === undefined ? this.timeout : arguments[0];
399
+
400
+ if (this.joinedOnce) {
401
+ throw "tried to join multiple times. 'join' can only be called a single time per channel instance";
402
+ } else {
403
+ this.joinedOnce = true;
404
+ }
405
+ this.rejoin(timeout);
406
+ return this.joinPush;
407
+ };
408
+
409
+ Channel.prototype.onClose = function onClose(callback) {
410
+ this.on(CHANNEL_EVENTS.close, callback);
411
+ };
412
+
413
+ Channel.prototype.onError = function onError(callback) {
414
+ this.on(CHANNEL_EVENTS.error, function (reason) {
415
+ return callback(reason);
416
+ });
417
+ };
418
+
419
+ Channel.prototype.on = function on(event, callback) {
420
+ this.bindings.push({ event: event, callback: callback });
421
+ };
422
+
423
+ Channel.prototype.off = function off(event) {
424
+ this.bindings = this.bindings.filter(function (bind) {
425
+ return bind.event !== event;
426
+ });
427
+ };
428
+
429
+ Channel.prototype.canPush = function canPush() {
430
+ return this.socket.isConnected() && this.state === CHANNEL_STATES.joined;
431
+ };
432
+
433
+ Channel.prototype.push = function push(event, payload) {
434
+ var timeout = arguments.length <= 2 || arguments[2] === undefined ? this.timeout : arguments[2];
435
+
436
+ if (!this.joinedOnce) {
437
+ throw "tried to push '" + event + "' to '" + this.topic + "' before joining. Use channel.join() before pushing events";
438
+ }
439
+ var pushEvent = new Push(this, event, payload, timeout);
440
+ if (this.canPush()) {
441
+ pushEvent.send();
442
+ } else {
443
+ pushEvent.startTimeout();
444
+ this.pushBuffer.push(pushEvent);
445
+ }
446
+
447
+ return pushEvent;
448
+ };
449
+
450
+ // Leaves the channel
451
+ //
452
+ // Unsubscribes from server events, and
453
+ // instructs channel to terminate on server
454
+ //
455
+ // Triggers onClose() hooks
456
+ //
457
+ // To receive leave acknowledgements, use the a `receive`
458
+ // hook to bind to the server ack, ie:
459
+ //
460
+ // channel.leave().receive("ok", () => alert("left!") )
461
+ //
462
+
463
+ Channel.prototype.leave = function leave() {
464
+ var _this3 = this;
465
+
466
+ var timeout = arguments.length <= 0 || arguments[0] === undefined ? this.timeout : arguments[0];
467
+
468
+ var onClose = function onClose() {
469
+ _this3.socket.log("channel", "leave " + _this3.topic);
470
+ _this3.trigger(CHANNEL_EVENTS.close, "leave");
471
+ };
472
+ var leavePush = new Push(this, CHANNEL_EVENTS.leave, {}, timeout);
473
+ leavePush.receive("ok", function () {
474
+ return onClose();
475
+ }).receive("timeout", function () {
476
+ return onClose();
477
+ });
478
+ leavePush.send();
479
+ if (!this.canPush()) {
480
+ leavePush.trigger("ok", {});
481
+ }
482
+
483
+ return leavePush;
484
+ };
485
+
486
+ // Overridable message hook
487
+ //
488
+ // Receives all events for specialized message handling
489
+
490
+ Channel.prototype.onMessage = function onMessage(event, payload, ref) {};
491
+
492
+ // private
493
+
494
+ Channel.prototype.isMember = function isMember(topic) {
495
+ return this.topic === topic;
496
+ };
497
+
498
+ Channel.prototype.sendJoin = function sendJoin(timeout) {
499
+ this.state = CHANNEL_STATES.joining;
500
+ this.joinPush.resend(timeout);
501
+ };
502
+
503
+ Channel.prototype.rejoin = function rejoin() {
504
+ var timeout = arguments.length <= 0 || arguments[0] === undefined ? this.timeout : arguments[0];
505
+ this.sendJoin(timeout);
506
+ };
507
+
508
+ Channel.prototype.trigger = function trigger(triggerEvent, payload, ref) {
509
+ this.onMessage(triggerEvent, payload, ref);
510
+ this.bindings.filter(function (bind) {
511
+ return bind.event === triggerEvent;
512
+ }).map(function (bind) {
513
+ return bind.callback(payload, ref);
514
+ });
515
+ };
516
+
517
+ Channel.prototype.replyEventName = function replyEventName(ref) {
518
+ return "chan_reply_" + ref;
519
+ };
520
+
521
+ return Channel;
522
+ })();
523
+
524
+ exports.Channel = Channel;
525
+
526
+ var Socket = (function () {
527
+
528
+ // Initializes the Socket
529
+ //
530
+ // endPoint - The string WebSocket endpoint, ie, "ws://example.com/ws",
531
+ // "wss://example.com"
532
+ // "/ws" (inherited host & protocol)
533
+ // opts - Optional configuration
534
+ // transport - The Websocket Transport, for example WebSocket or Phoenix.LongPoll.
535
+ // Defaults to WebSocket with automatic LongPoll fallback.
536
+ // timeout - The default timeout in milliseconds to trigger push timeouts.
537
+ // Defaults `DEFAULT_TIMEOUT`
538
+ // heartbeatIntervalMs - The millisec interval to send a heartbeat message
539
+ // reconnectAfterMs - The optional function that returns the millsec
540
+ // reconnect interval. Defaults to stepped backoff of:
541
+ //
542
+ // function(tries){
543
+ // return [1000, 5000, 10000][tries - 1] || 10000
544
+ // }
545
+ //
546
+ // logger - The optional function for specialized logging, ie:
547
+ // `logger: (kind, msg, data) => { console.log(`${kind}: ${msg}`, data) }
548
+ //
549
+ // longpollerTimeout - The maximum timeout of a long poll AJAX request.
550
+ // Defaults to 20s (double the server long poll timer).
551
+ //
552
+ // params - The optional params to pass when connecting
553
+ //
554
+ // For IE8 support use an ES5-shim (https://github.com/es-shims/es5-shim)
555
+ //
556
+
557
+ function Socket(endPoint) {
558
+ var _this4 = this;
559
+
560
+ var opts = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1];
561
+
562
+ _classCallCheck(this, Socket);
563
+
564
+ this.stateChangeCallbacks = { open: [], close: [], error: [], message: [] };
565
+ this.channels = [];
566
+ this.sendBuffer = [];
567
+ this.ref = 0;
568
+ this.timeout = opts.timeout || DEFAULT_TIMEOUT;
569
+ this.transport = opts.transport || window.WebSocket || LongPoll;
570
+ this.heartbeatIntervalMs = opts.heartbeatIntervalMs || 30000;
571
+ this.reconnectAfterMs = opts.reconnectAfterMs || function (tries) {
572
+ return [1000, 2000, 5000, 10000][tries - 1] || 10000;
573
+ };
574
+ this.logger = opts.logger || function () {}; // noop
575
+ this.longpollerTimeout = opts.longpollerTimeout || 20000;
576
+ this.params = opts.params || {};
577
+ this.endPoint = endPoint + "/" + TRANSPORTS.websocket;
578
+ this.reconnectTimer = new Timer(function () {
579
+ _this4.disconnect(function () {
580
+ return _this4.connect();
581
+ });
582
+ }, this.reconnectAfterMs);
583
+ }
584
+
585
+ Socket.prototype.protocol = function protocol() {
586
+ return location.protocol.match(/^https/) ? "wss" : "ws";
587
+ };
588
+
589
+ Socket.prototype.endPointURL = function endPointURL() {
590
+ var uri = Ajax.appendParams(Ajax.appendParams(this.endPoint, this.params), { vsn: VSN });
591
+ if (uri.charAt(0) !== "/") {
592
+ return uri;
593
+ }
594
+ if (uri.charAt(1) === "/") {
595
+ return this.protocol() + ":" + uri;
596
+ }
597
+
598
+ return this.protocol() + "://" + location.host + uri;
599
+ };
600
+
601
+ Socket.prototype.disconnect = function disconnect(callback, code, reason) {
602
+ if (this.conn) {
603
+ this.conn.onclose = function () {}; // noop
604
+ if (code) {
605
+ this.conn.close(code, reason || "");
606
+ } else {
607
+ this.conn.close();
608
+ }
609
+ this.conn = null;
610
+ }
611
+ callback && callback();
612
+ };
613
+
614
+ // params - The params to send when connecting, for example `{user_id: userToken}`
615
+
616
+ Socket.prototype.connect = function connect(params) {
617
+ var _this5 = this;
618
+
619
+ if (params) {
620
+ console && console.log("passing params to connect is deprecated. Instead pass :params to the Socket constructor");
621
+ this.params = params;
622
+ }
623
+ if (this.conn) {
624
+ return;
625
+ }
626
+
627
+ this.conn = new this.transport(this.endPointURL());
628
+ this.conn.timeout = this.longpollerTimeout;
629
+ this.conn.onopen = function () {
630
+ return _this5.onConnOpen();
631
+ };
632
+ this.conn.onerror = function (error) {
633
+ return _this5.onConnError(error);
634
+ };
635
+ this.conn.onmessage = function (event) {
636
+ return _this5.onConnMessage(event);
637
+ };
638
+ this.conn.onclose = function (event) {
639
+ return _this5.onConnClose(event);
640
+ };
641
+ };
642
+
643
+ // Logs the message. Override `this.logger` for specialized logging. noops by default
644
+
645
+ Socket.prototype.log = function log(kind, msg, data) {
646
+ this.logger(kind, msg, data);
647
+ };
648
+
649
+ // Registers callbacks for connection state change events
650
+ //
651
+ // Examples
652
+ //
653
+ // socket.onError(function(error){ alert("An error occurred") })
654
+ //
655
+
656
+ Socket.prototype.onOpen = function onOpen(callback) {
657
+ this.stateChangeCallbacks.open.push(callback);
658
+ };
659
+
660
+ Socket.prototype.onClose = function onClose(callback) {
661
+ this.stateChangeCallbacks.close.push(callback);
662
+ };
663
+
664
+ Socket.prototype.onError = function onError(callback) {
665
+ this.stateChangeCallbacks.error.push(callback);
666
+ };
667
+
668
+ Socket.prototype.onMessage = function onMessage(callback) {
669
+ this.stateChangeCallbacks.message.push(callback);
670
+ };
671
+
672
+ Socket.prototype.onConnOpen = function onConnOpen() {
673
+ var _this6 = this;
674
+
675
+ this.log("transport", "connected to " + this.endPointURL(), this.transport.prototype);
676
+ this.flushSendBuffer();
677
+ this.reconnectTimer.reset();
678
+ if (!this.conn.skipHeartbeat) {
679
+ clearInterval(this.heartbeatTimer);
680
+ this.heartbeatTimer = setInterval(function () {
681
+ return _this6.sendHeartbeat();
682
+ }, this.heartbeatIntervalMs);
683
+ }
684
+ this.stateChangeCallbacks.open.forEach(function (callback) {
685
+ return callback();
686
+ });
687
+ };
688
+
689
+ Socket.prototype.onConnClose = function onConnClose(event) {
690
+ this.log("transport", "close", event);
691
+ this.triggerChanError();
692
+ clearInterval(this.heartbeatTimer);
693
+ this.reconnectTimer.setTimeout();
694
+ this.stateChangeCallbacks.close.forEach(function (callback) {
695
+ return callback(event);
696
+ });
697
+ };
698
+
699
+ Socket.prototype.onConnError = function onConnError(error) {
700
+ this.log("transport", error);
701
+ this.triggerChanError();
702
+ this.stateChangeCallbacks.error.forEach(function (callback) {
703
+ return callback(error);
704
+ });
705
+ };
706
+
707
+ Socket.prototype.triggerChanError = function triggerChanError() {
708
+ this.channels.forEach(function (channel) {
709
+ return channel.trigger(CHANNEL_EVENTS.error);
710
+ });
711
+ };
712
+
713
+ Socket.prototype.connectionState = function connectionState() {
714
+ switch (this.conn && this.conn.readyState) {
715
+ case SOCKET_STATES.connecting:
716
+ return "connecting";
717
+ case SOCKET_STATES.open:
718
+ return "open";
719
+ case SOCKET_STATES.closing:
720
+ return "closing";
721
+ default:
722
+ return "closed";
723
+ }
724
+ };
725
+
726
+ Socket.prototype.isConnected = function isConnected() {
727
+ return this.connectionState() === "open";
728
+ };
729
+
730
+ Socket.prototype.remove = function remove(channel) {
731
+ this.channels = this.channels.filter(function (c) {
732
+ return !c.isMember(channel.topic);
733
+ });
734
+ };
735
+
736
+ Socket.prototype.channel = function channel(topic) {
737
+ var chanParams = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1];
738
+
739
+ var chan = new Channel(topic, chanParams, this);
740
+ this.channels.push(chan);
741
+ return chan;
742
+ };
743
+
744
+ Socket.prototype.push = function push(data) {
745
+ var _this7 = this;
746
+
747
+ var topic = data.topic;
748
+ var event = data.event;
749
+ var payload = data.payload;
750
+ var ref = data.ref;
751
+
752
+ var callback = function callback() {
753
+ return _this7.conn.send(JSON.stringify(data));
754
+ };
755
+ this.log("push", topic + " " + event + " (" + ref + ")", payload);
756
+ if (this.isConnected()) {
757
+ callback();
758
+ } else {
759
+ this.sendBuffer.push(callback);
760
+ }
761
+ };
762
+
763
+ // Return the next message ref, accounting for overflows
764
+
765
+ Socket.prototype.makeRef = function makeRef() {
766
+ var newRef = this.ref + 1;
767
+ if (newRef === this.ref) {
768
+ this.ref = 0;
769
+ } else {
770
+ this.ref = newRef;
771
+ }
772
+
773
+ return this.ref.toString();
774
+ };
775
+
776
+ Socket.prototype.sendHeartbeat = function sendHeartbeat() {
777
+ if (!this.isConnected()) {
778
+ return;
779
+ }
780
+ this.push({ topic: "phoenix", event: "heartbeat", payload: {}, ref: this.makeRef() });
781
+ };
782
+
783
+ Socket.prototype.flushSendBuffer = function flushSendBuffer() {
784
+ if (this.isConnected() && this.sendBuffer.length > 0) {
785
+ this.sendBuffer.forEach(function (callback) {
786
+ return callback();
787
+ });
788
+ this.sendBuffer = [];
789
+ }
790
+ };
791
+
792
+ Socket.prototype.onConnMessage = function onConnMessage(rawMessage) {
793
+ var msg = JSON.parse(rawMessage.data);
794
+ var topic = msg.topic;
795
+ var event = msg.event;
796
+ var payload = msg.payload;
797
+ var ref = msg.ref;
798
+
799
+ this.log("receive", (payload.status || "") + " " + topic + " " + event + " " + (ref && "(" + ref + ")" || ""), payload);
800
+ this.channels.filter(function (channel) {
801
+ return channel.isMember(topic);
802
+ }).forEach(function (channel) {
803
+ return channel.trigger(event, payload, ref);
804
+ });
805
+ this.stateChangeCallbacks.message.forEach(function (callback) {
806
+ return callback(msg);
807
+ });
808
+ };
809
+
810
+ return Socket;
811
+ })();
812
+
813
+ exports.Socket = Socket;
814
+
815
+ var LongPoll = (function () {
816
+ function LongPoll(endPoint) {
817
+ _classCallCheck(this, LongPoll);
818
+
819
+ this.endPoint = null;
820
+ this.token = null;
821
+ this.skipHeartbeat = true;
822
+ this.onopen = function () {}; // noop
823
+ this.onerror = function () {}; // noop
824
+ this.onmessage = function () {}; // noop
825
+ this.onclose = function () {}; // noop
826
+ this.pollEndpoint = this.normalizeEndpoint(endPoint);
827
+ this.readyState = SOCKET_STATES.connecting;
828
+
829
+ this.poll();
830
+ }
831
+
832
+ LongPoll.prototype.normalizeEndpoint = function normalizeEndpoint(endPoint) {
833
+ return endPoint.replace("ws://", "http://").replace("wss://", "https://").replace(new RegExp("(.*)\/" + TRANSPORTS.websocket), "$1/" + TRANSPORTS.longpoll);
834
+ };
835
+
836
+ LongPoll.prototype.endpointURL = function endpointURL() {
837
+ return Ajax.appendParams(this.pollEndpoint, { token: this.token });
838
+ };
839
+
840
+ LongPoll.prototype.closeAndRetry = function closeAndRetry() {
841
+ this.close();
842
+ this.readyState = SOCKET_STATES.connecting;
843
+ };
844
+
845
+ LongPoll.prototype.ontimeout = function ontimeout() {
846
+ this.onerror("timeout");
847
+ this.closeAndRetry();
848
+ };
849
+
850
+ LongPoll.prototype.poll = function poll() {
851
+ var _this8 = this;
852
+
853
+ if (!(this.readyState === SOCKET_STATES.open || this.readyState === SOCKET_STATES.connecting)) {
854
+ return;
855
+ }
856
+
857
+ Ajax.request("GET", this.endpointURL(), "application/json", null, this.timeout, this.ontimeout.bind(this), function (resp) {
858
+ if (resp) {
859
+ var status = resp.status;
860
+ var token = resp.token;
861
+ var messages = resp.messages;
862
+
863
+ _this8.token = token;
864
+ } else {
865
+ var status = 0;
866
+ }
867
+
868
+ switch (status) {
869
+ case 200:
870
+ messages.forEach(function (msg) {
871
+ return _this8.onmessage({ data: JSON.stringify(msg) });
872
+ });
873
+ _this8.poll();
874
+ break;
875
+ case 204:
876
+ _this8.poll();
877
+ break;
878
+ case 410:
879
+ _this8.readyState = SOCKET_STATES.open;
880
+ _this8.onopen();
881
+ _this8.poll();
882
+ break;
883
+ case 0:
884
+ case 500:
885
+ _this8.onerror();
886
+ _this8.closeAndRetry();
887
+ break;
888
+ default:
889
+ throw "unhandled poll status " + status;
890
+ }
891
+ });
892
+ };
893
+
894
+ LongPoll.prototype.send = function send(body) {
895
+ var _this9 = this;
896
+
897
+ Ajax.request("POST", this.endpointURL(), "application/json", body, this.timeout, this.onerror.bind(this, "timeout"), function (resp) {
898
+ if (!resp || resp.status !== 200) {
899
+ _this9.onerror(status);
900
+ _this9.closeAndRetry();
901
+ }
902
+ });
903
+ };
904
+
905
+ LongPoll.prototype.close = function close(code, reason) {
906
+ this.readyState = SOCKET_STATES.closed;
907
+ this.onclose();
908
+ };
909
+
910
+ return LongPoll;
911
+ })();
912
+
913
+ exports.LongPoll = LongPoll;
914
+
915
+ var Ajax = (function () {
916
+ function Ajax() {
917
+ _classCallCheck(this, Ajax);
918
+ }
919
+
920
+ Ajax.request = function request(method, endPoint, accept, body, timeout, ontimeout, callback) {
921
+ if (window.XDomainRequest) {
922
+ var req = new XDomainRequest(); // IE8, IE9
923
+ this.xdomainRequest(req, method, endPoint, body, timeout, ontimeout, callback);
924
+ } else {
925
+ var req = window.XMLHttpRequest ? new XMLHttpRequest() : // IE7+, Firefox, Chrome, Opera, Safari
926
+ new ActiveXObject("Microsoft.XMLHTTP"); // IE6, IE5
927
+ this.xhrRequest(req, method, endPoint, accept, body, timeout, ontimeout, callback);
928
+ }
929
+ };
930
+
931
+ Ajax.xdomainRequest = function xdomainRequest(req, method, endPoint, body, timeout, ontimeout, callback) {
932
+ var _this10 = this;
933
+
934
+ req.timeout = timeout;
935
+ req.open(method, endPoint);
936
+ req.onload = function () {
937
+ var response = _this10.parseJSON(req.responseText);
938
+ callback && callback(response);
939
+ };
940
+ if (ontimeout) {
941
+ req.ontimeout = ontimeout;
942
+ }
943
+
944
+ // Work around bug in IE9 that requires an attached onprogress handler
945
+ req.onprogress = function () {};
946
+
947
+ req.send(body);
948
+ };
949
+
950
+ Ajax.xhrRequest = function xhrRequest(req, method, endPoint, accept, body, timeout, ontimeout, callback) {
951
+ var _this11 = this;
952
+
953
+ req.timeout = timeout;
954
+ req.open(method, endPoint, true);
955
+ req.setRequestHeader("Content-Type", accept);
956
+ req.onerror = function () {
957
+ callback && callback(null);
958
+ };
959
+ req.onreadystatechange = function () {
960
+ if (req.readyState === _this11.states.complete && callback) {
961
+ var response = _this11.parseJSON(req.responseText);
962
+ callback(response);
963
+ }
964
+ };
965
+ if (ontimeout) {
966
+ req.ontimeout = ontimeout;
967
+ }
968
+
969
+ req.send(body);
970
+ };
971
+
972
+ Ajax.parseJSON = function parseJSON(resp) {
973
+ return resp && resp !== "" ? JSON.parse(resp) : null;
974
+ };
975
+
976
+ Ajax.serialize = function serialize(obj, parentKey) {
977
+ var queryStr = [];
978
+ for (var key in obj) {
979
+ if (!obj.hasOwnProperty(key)) {
980
+ continue;
981
+ }
982
+ var paramKey = parentKey ? parentKey + "[" + key + "]" : key;
983
+ var paramVal = obj[key];
984
+ if (typeof paramVal === "object") {
985
+ queryStr.push(this.serialize(paramVal, paramKey));
986
+ } else {
987
+ queryStr.push(encodeURIComponent(paramKey) + "=" + encodeURIComponent(paramVal));
988
+ }
989
+ }
990
+ return queryStr.join("&");
991
+ };
992
+
993
+ Ajax.appendParams = function appendParams(url, params) {
994
+ if (Object.keys(params).length === 0) {
995
+ return url;
996
+ }
997
+
998
+ var prefix = url.match(/\?/) ? "&" : "?";
999
+ return "" + url + prefix + this.serialize(params);
1000
+ };
1001
+
1002
+ return Ajax;
1003
+ })();
1004
+
1005
+ exports.Ajax = Ajax;
1006
+
1007
+ Ajax.states = { complete: 4 };
1008
+
1009
+ // Creates a timer that accepts a `timerCalc` function to perform
1010
+ // calculated timeout retries, such as exponential backoff.
1011
+ //
1012
+ // ## Examples
1013
+ //
1014
+ // let reconnectTimer = new Timer(() => this.connect(), function(tries){
1015
+ // return [1000, 5000, 10000][tries - 1] || 10000
1016
+ // })
1017
+ // reconnectTimer.setTimeout() // fires after 1000
1018
+ // reconnectTimer.setTimeout() // fires after 5000
1019
+ // reconnectTimer.reset()
1020
+ // reconnectTimer.setTimeout() // fires after 1000
1021
+ //
1022
+
1023
+ var Timer = (function () {
1024
+ function Timer(callback, timerCalc) {
1025
+ _classCallCheck(this, Timer);
1026
+
1027
+ this.callback = callback;
1028
+ this.timerCalc = timerCalc;
1029
+ this.timer = null;
1030
+ this.tries = 0;
1031
+ }
1032
+
1033
+ Timer.prototype.reset = function reset() {
1034
+ this.tries = 0;
1035
+ clearTimeout(this.timer);
1036
+ };
1037
+
1038
+ // Cancels any previous setTimeout and schedules callback
1039
+
1040
+ Timer.prototype.setTimeout = (function (_setTimeout) {
1041
+ function setTimeout() {
1042
+ return _setTimeout.apply(this, arguments);
1043
+ }
1044
+
1045
+ setTimeout.toString = function () {
1046
+ return _setTimeout.toString();
1047
+ };
1048
+
1049
+ return setTimeout;
1050
+ })(function () {
1051
+ var _this12 = this;
1052
+
1053
+ clearTimeout(this.timer);
1054
+
1055
+ this.timer = setTimeout(function () {
1056
+ _this12.tries = _this12.tries + 1;
1057
+ _this12.callback();
1058
+ }, this.timerCalc(this.tries + 1));
1059
+ });
1060
+
1061
+ return Timer;
1062
+ })();
1063
+
1064
+ }});
1065
+ if(typeof(window) === 'object' && !window.Phoenix){ window.Phoenix = require('phoenix') };
metadata ADDED
@@ -0,0 +1,82 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: opal-phoenix
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Michał Kalbarczyk
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-11-15 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: opal
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ description: Phoenix client wrapper for opal
42
+ email: fazibear@gmail.com
43
+ executables: []
44
+ extensions: []
45
+ extra_rdoc_files: []
46
+ files:
47
+ - ".gitignore"
48
+ - Gemfile
49
+ - README.md
50
+ - Rakefile
51
+ - lib/opal-phoenix.rb
52
+ - opal-phoenix.gemspec
53
+ - opal/opal-phoenix.rb
54
+ - opal/phoenix.rb
55
+ - opal/phoenix/channel.rb
56
+ - opal/phoenix/push.rb
57
+ - opal/phoenix/socket.rb
58
+ - opal/phoenix_js.js
59
+ homepage: http://github.com/fazibear/opal-phoenix
60
+ licenses: []
61
+ metadata: {}
62
+ post_install_message:
63
+ rdoc_options: []
64
+ require_paths:
65
+ - lib
66
+ required_ruby_version: !ruby/object:Gem::Requirement
67
+ requirements:
68
+ - - ">="
69
+ - !ruby/object:Gem::Version
70
+ version: '0'
71
+ required_rubygems_version: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ requirements: []
77
+ rubyforge_project:
78
+ rubygems_version: 2.4.8
79
+ signing_key:
80
+ specification_version: 4
81
+ summary: Phoenix client wrapper for opal
82
+ test_files: []