opal-phoenix 0.0.1

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.
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: []