├── LICENSE ├── README.md ├── action_cable_react_jwt.js └── package.json /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Saravanabalagi R 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # action-cable-react-jwt 2 | 3 | Same as [action-cable-react](https://github.com/schneidmaster/action-cable-react), but allows authenticating websockets using JWTs 4 | 5 | ## Installation 6 | 7 | Yarn: 8 | 9 | ```javascript 10 | yarn add action-cable-react-jwt 11 | 12 | ``` 13 | 14 | npm: 15 | 16 | ```javascript 17 | npm install action-cable-react-jwt 18 | 19 | ``` 20 | 21 | 22 | ## Usage 23 | 24 | Import action-cable-react-jwt 25 | 26 | ```javascript 27 | import ActionCable from 'action-cable-react-jwt.js'; 28 | 29 | // if you don't use ES6 then use 30 | // const ActionCable = require('action-cable-react-jwt.js'); 31 | 32 | ``` 33 | 34 | Creating an actioncable websocket 35 | 36 | ```javascript 37 | let App = {}; 38 | App.cable = ActionCable.createConsumer("ws://cable.example.com", jwt) // place your jwt here 39 | 40 | // you shall also use this.cable = ActionCable.createConsumer(...) 41 | // to create the connection as soon as the view loads, place this in componentDidMount 42 | ``` 43 | 44 | Subscribing to a Channel for Receiving data 45 | 46 | ```javascript 47 | this.subscription = App.cable.subscriptions.create({channel: "YourChannel"}, { 48 | connected: function() { console.log("cable: connected") }, // onConnect 49 | disconnected: function() { console.log("cable: disconnected") }, // onDisconnect 50 | received: (data) => { console.log("cable received: ", data); } // OnReceive 51 | } 52 | 53 | ``` 54 | 55 | Send data to a channel 56 | 57 | ```javascript 58 | this.subscription.send('hello world') 59 | 60 | ``` 61 | 62 | Call a method on channel with arguments 63 | 64 | ```javascript 65 | this.subscription.perform('method_name', arguments) 66 | 67 | ``` 68 | 69 | In your `ApplicationCable::Connection` class in Ruby add 70 | 71 | ```ruby 72 | # app/channels/application_cable/connection.rb 73 | module ApplicationCable 74 | class Connection < ActionCable::Connection::Base 75 | identified_by :current_user 76 | 77 | def connect 78 | self.current_user = find_verified_user 79 | end 80 | 81 | private 82 | 83 | def find_verified_user 84 | begin 85 | header_array = request.headers[:HTTP_SEC_WEBSOCKET_PROTOCOL].split(',') 86 | token = header_array[header_array.length-1] 87 | decoded_token = JWT.decode token.strip, Rails.application.secrets.secret_key_base, true, { :algorithm => 'HS256' } 88 | if (current_user = User.find((decoded_token[0])['sub'])) 89 | current_user 90 | else 91 | reject_unauthorized_connection 92 | end 93 | rescue 94 | reject_unauthorized_connection 95 | end 96 | end 97 | 98 | end 99 | end 100 | ``` 101 | 102 | And in YourChannel.rb 103 | 104 | ```ruby 105 | # app/channels/you_channel.rb 106 | class LocationChannel < ApplicationCable::Channel 107 | 108 | # calls connect in client 109 | def subscribed 110 | stream_from 'location_user_' + current_user.id.to_s 111 | end 112 | 113 | # calls disconnect in client 114 | def unsubscribed 115 | # Any cleanup needed when channel is unsubscribed 116 | end 117 | 118 | # called when send is called in client 119 | def receive(params) 120 | print params[:data] 121 | end 122 | 123 | # called when perform is called in client 124 | def method_name(params) 125 | print params[:data] 126 | end 127 | 128 | end 129 | ``` 130 | 131 | Remove a subscription from cable 132 | 133 | ```javascript 134 | App.cable.subscriptions.remove(this.subscription) 135 | 136 | // Place this in componentWillUnmount to remove subscription on exiting app 137 | 138 | ``` 139 | 140 | Add a subscription to cable 141 | 142 | ```javascript 143 | App.cable.subscriptions.add(this.subscription) 144 | 145 | ``` 146 | 147 | Querying url and jwt from cable 148 | 149 | ```javascript 150 | console.log(App.cable.jwt); 151 | console.log(App.cable.url); 152 | 153 | ``` 154 | 155 | Querying subscriptions and connection from cable 156 | 157 | ```javascript 158 | console.log(App.cable.subscriptions); 159 | console.log(App.cable.connection); 160 | 161 | ``` 162 | 163 | 164 | 165 | ## License 166 | 167 | MIT 168 | 169 | Copyright (c) 2017 Zeke 170 | 171 | Permission is hereby granted, free of charge, to any person obtaining a copy 172 | of this software and associated documentation files (the "Software"), to deal 173 | in the Software without restriction, including without limitation the rights 174 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 175 | copies of the Software, and to permit persons to whom the Software is 176 | furnished to do so, subject to the following conditions: 177 | 178 | The above copyright notice and this permission notice shall be included in all 179 | copies or substantial portions of the Software. 180 | 181 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 182 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 183 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 184 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 185 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 186 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 187 | SOFTWARE. 188 | 189 | 190 | -------------------------------------------------------------------------------- /action_cable_react_jwt.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | (function() { 3 | (function() { 4 | var slice = [].slice; 5 | 6 | global.document = { 7 | addEventListener: function() {}, 8 | removeEventListener: function() {} 9 | } 10 | 11 | this.ActionCable = { 12 | INTERNAL: { 13 | "message_types": { 14 | "welcome": "welcome", 15 | "ping": "ping", 16 | "confirmation": "confirm_subscription", 17 | "rejection": "reject_subscription" 18 | }, 19 | "default_mount_path": "/cable", 20 | "protocols": ["actioncable-v1-json", "actioncable-unsupported"] 21 | }, 22 | createConsumer: function(url, jwt) { 23 | var ref; 24 | if (url == null) { 25 | url = (ref = this.getConfig("url")) != null ? ref : this.INTERNAL.default_mount_path; 26 | } 27 | return new ActionCable.Consumer(this.createWebSocketURL(url), jwt); 28 | }, 29 | getConfig: function(name) { 30 | if(!document.head) return null; 31 | var element; 32 | element = document.head.querySelector("meta[name='action-cable-" + name + "']"); 33 | return element != null ? element.getAttribute("content") : void 0; 34 | }, 35 | createWebSocketURL: function(url) { 36 | var a; 37 | if (url && !/^wss?:/i.test(url) 38 | && Object.getOwnPropertyNames(document).includes("createElement")) { 39 | a = document.createElement("a"); 40 | a.href = url; 41 | a.href = a.href; 42 | a.protocol = a.protocol.replace("http", "ws"); 43 | return a.href; 44 | } else { 45 | return url.replace(/^http/, 'ws'); 46 | } 47 | }, 48 | startDebugging: function() { 49 | return this.debugging = true; 50 | }, 51 | stopDebugging: function() { 52 | return this.debugging = null; 53 | }, 54 | log: function() { 55 | var messages; 56 | messages = 1 <= arguments.length ? slice.call(arguments, 0) : []; 57 | if (this.debugging) { 58 | messages.push(Date.now()); 59 | return console.log.apply(console, ["[ActionCable]"].concat(slice.call(messages))); 60 | } 61 | } 62 | }; 63 | 64 | }).call(this); 65 | }).call(this); 66 | 67 | var ActionCable = this.ActionCable; 68 | 69 | (function() { 70 | (function() { 71 | var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }; 72 | 73 | ActionCable.ConnectionMonitor = (function() { 74 | var clamp, now, secondsSince; 75 | 76 | ConnectionMonitor.pollInterval = { 77 | min: 3, 78 | max: 30 79 | }; 80 | 81 | ConnectionMonitor.staleThreshold = 6; 82 | 83 | function ConnectionMonitor(connection) { 84 | this.connection = connection; 85 | this.visibilityDidChange = bind(this.visibilityDidChange, this); 86 | this.reconnectAttempts = 0; 87 | } 88 | 89 | ConnectionMonitor.prototype.start = function() { 90 | if (!this.isRunning()) { 91 | this.startedAt = now(); 92 | delete this.stoppedAt; 93 | this.startPolling(); 94 | document.addEventListener("visibilitychange", this.visibilityDidChange); 95 | return ActionCable.log("ConnectionMonitor started. pollInterval = " + (this.getPollInterval()) + " ms"); 96 | } 97 | }; 98 | 99 | ConnectionMonitor.prototype.stop = function() { 100 | if (this.isRunning()) { 101 | this.stoppedAt = now(); 102 | this.stopPolling(); 103 | document.removeEventListener("visibilitychange", this.visibilityDidChange); 104 | return ActionCable.log("ConnectionMonitor stopped"); 105 | } 106 | }; 107 | 108 | ConnectionMonitor.prototype.isRunning = function() { 109 | return (this.startedAt != null) && (this.stoppedAt == null); 110 | }; 111 | 112 | ConnectionMonitor.prototype.recordPing = function() { 113 | return this.pingedAt = now(); 114 | }; 115 | 116 | ConnectionMonitor.prototype.recordConnect = function() { 117 | this.reconnectAttempts = 0; 118 | this.recordPing(); 119 | delete this.disconnectedAt; 120 | return ActionCable.log("ConnectionMonitor recorded connect"); 121 | }; 122 | 123 | ConnectionMonitor.prototype.recordDisconnect = function() { 124 | this.disconnectedAt = now(); 125 | return ActionCable.log("ConnectionMonitor recorded disconnect"); 126 | }; 127 | 128 | ConnectionMonitor.prototype.startPolling = function() { 129 | this.stopPolling(); 130 | return this.poll(); 131 | }; 132 | 133 | ConnectionMonitor.prototype.stopPolling = function() { 134 | return clearTimeout(this.pollTimeout); 135 | }; 136 | 137 | ConnectionMonitor.prototype.poll = function() { 138 | return this.pollTimeout = setTimeout((function(_this) { 139 | return function() { 140 | _this.reconnectIfStale(); 141 | return _this.poll(); 142 | }; 143 | })(this), this.getPollInterval()); 144 | }; 145 | 146 | ConnectionMonitor.prototype.getPollInterval = function() { 147 | var interval, max, min, ref; 148 | ref = this.constructor.pollInterval, min = ref.min, max = ref.max; 149 | interval = 5 * Math.log(this.reconnectAttempts + 1); 150 | return Math.round(clamp(interval, min, max) * 1000); 151 | }; 152 | 153 | ConnectionMonitor.prototype.reconnectIfStale = function() { 154 | if (this.connectionIsStale()) { 155 | ActionCable.log("ConnectionMonitor detected stale connection. reconnectAttempts = " + this.reconnectAttempts + ", pollInterval = " + (this.getPollInterval()) + " ms, time disconnected = " + (secondsSince(this.disconnectedAt)) + " s, stale threshold = " + this.constructor.staleThreshold + " s"); 156 | this.reconnectAttempts++; 157 | if (this.disconnectedRecently()) { 158 | return ActionCable.log("ConnectionMonitor skipping reopening recent disconnect"); 159 | } else { 160 | ActionCable.log("ConnectionMonitor reopening"); 161 | return this.connection.reopen(); 162 | } 163 | } 164 | }; 165 | 166 | ConnectionMonitor.prototype.connectionIsStale = function() { 167 | var ref; 168 | return secondsSince((ref = this.pingedAt) != null ? ref : this.startedAt) > this.constructor.staleThreshold; 169 | }; 170 | 171 | ConnectionMonitor.prototype.disconnectedRecently = function() { 172 | return this.disconnectedAt && secondsSince(this.disconnectedAt) < this.constructor.staleThreshold; 173 | }; 174 | 175 | ConnectionMonitor.prototype.visibilityDidChange = function() { 176 | if (document.visibilityState === "visible") { 177 | return setTimeout((function(_this) { 178 | return function() { 179 | if (_this.connectionIsStale() || !_this.connection.isOpen()) { 180 | ActionCable.log("ConnectionMonitor reopening stale connection on visibilitychange. visbilityState = " + document.visibilityState); 181 | return _this.connection.reopen(); 182 | } 183 | }; 184 | })(this), 200); 185 | } 186 | }; 187 | 188 | now = function() { 189 | return new Date().getTime(); 190 | }; 191 | 192 | secondsSince = function(time) { 193 | return (now() - time) / 1000; 194 | }; 195 | 196 | clamp = function(number, min, max) { 197 | return Math.max(min, Math.min(max, number)); 198 | }; 199 | 200 | return ConnectionMonitor; 201 | 202 | })(); 203 | 204 | }).call(this); 205 | (function() { 206 | var i, message_types, protocols, ref, supportedProtocols, unsupportedProtocol, 207 | slice = [].slice, 208 | bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }, 209 | indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }; 210 | 211 | ref = ActionCable.INTERNAL, message_types = ref.message_types, protocols = ref.protocols; 212 | 213 | supportedProtocols = 2 <= protocols.length ? slice.call(protocols, 0, i = protocols.length - 1) : (i = 0, []), unsupportedProtocol = protocols[i++]; 214 | 215 | ActionCable.Connection = (function() { 216 | Connection.reopenDelay = 500; 217 | 218 | function Connection(consumer) { 219 | this.consumer = consumer; 220 | this.open = bind(this.open, this); 221 | this.subscriptions = this.consumer.subscriptions; 222 | this.monitor = new ActionCable.ConnectionMonitor(this); 223 | this.disconnected = true; 224 | } 225 | 226 | Connection.prototype.send = function(data) { 227 | if (this.isOpen()) { 228 | this.webSocket.send(JSON.stringify(data)); 229 | return true; 230 | } else { 231 | return false; 232 | } 233 | }; 234 | 235 | Connection.prototype.open = function() { 236 | if (this.isActive()) { 237 | ActionCable.log("Attempted to open WebSocket, but existing socket is " + (this.getState())); 238 | throw new Error("Existing connection must be closed before opening"); 239 | } else { 240 | ActionCable.log("Opening WebSocket, current state is " + (this.getState()) + ", subprotocols: " + protocols); 241 | if (this.webSocket != null) { 242 | this.uninstallEventHandlers(); 243 | } 244 | this.webSocket = new WebSocket(this.consumer.url, protocols.concat(this.consumer.jwt)); 245 | this.webSocket.protocol = 'actioncable-v1-json'; 246 | this.installEventHandlers(); 247 | this.monitor.start(); 248 | return true; 249 | } 250 | }; 251 | 252 | Connection.prototype.close = function(arg) { 253 | var allowReconnect, ref1; 254 | allowReconnect = (arg != null ? arg : { 255 | allowReconnect: true 256 | }).allowReconnect; 257 | if (!allowReconnect) { 258 | this.monitor.stop(); 259 | } 260 | if (this.isActive()) { 261 | return (ref1 = this.webSocket) != null ? ref1.close() : void 0; 262 | } 263 | }; 264 | 265 | Connection.prototype.reopen = function() { 266 | var error, error1; 267 | ActionCable.log("Reopening WebSocket, current state is " + (this.getState())); 268 | if (this.isActive()) { 269 | try { 270 | return this.close(); 271 | } catch (error1) { 272 | error = error1; 273 | return ActionCable.log("Failed to reopen WebSocket", error); 274 | } finally { 275 | ActionCable.log("Reopening WebSocket in " + this.constructor.reopenDelay + "ms"); 276 | setTimeout(this.open, this.constructor.reopenDelay); 277 | } 278 | } else { 279 | return this.open(); 280 | } 281 | }; 282 | 283 | Connection.prototype.getProtocol = function() { 284 | var ref1; 285 | return (ref1 = this.webSocket) != null ? ref1.protocol : void 0; 286 | }; 287 | 288 | Connection.prototype.isOpen = function() { 289 | return this.isState("open"); 290 | }; 291 | 292 | Connection.prototype.isActive = function() { 293 | return this.isState("open", "connecting"); 294 | }; 295 | 296 | Connection.prototype.isProtocolSupported = function() { 297 | var ref1; 298 | return ref1 = this.getProtocol(), indexOf.call(supportedProtocols, ref1) >= 0; 299 | }; 300 | 301 | Connection.prototype.isState = function() { 302 | var ref1, states; 303 | states = 1 <= arguments.length ? slice.call(arguments, 0) : []; 304 | return ref1 = this.getState(), indexOf.call(states, ref1) >= 0; 305 | }; 306 | 307 | Connection.prototype.getState = function() { 308 | var ref1, state, value; 309 | for (state in WebSocket) { 310 | value = WebSocket[state]; 311 | if (value === ((ref1 = this.webSocket) != null ? ref1.readyState : void 0)) { 312 | return state.toLowerCase(); 313 | } 314 | } 315 | return null; 316 | }; 317 | 318 | Connection.prototype.installEventHandlers = function() { 319 | var eventName, handler; 320 | for (eventName in this.events) { 321 | handler = this.events[eventName].bind(this); 322 | this.webSocket["on" + eventName] = handler; 323 | } 324 | }; 325 | 326 | Connection.prototype.uninstallEventHandlers = function() { 327 | var eventName; 328 | for (eventName in this.events) { 329 | this.webSocket["on" + eventName] = function() {}; 330 | } 331 | }; 332 | 333 | Connection.prototype.events = { 334 | message: function(event) { 335 | var identifier, message, ref1, type; 336 | if (!this.isProtocolSupported()) { 337 | return; 338 | } 339 | ref1 = JSON.parse(event.data), identifier = ref1.identifier, message = ref1.message, type = ref1.type; 340 | switch (type) { 341 | case message_types.welcome: 342 | this.monitor.recordConnect(); 343 | return this.subscriptions.reload(); 344 | case message_types.ping: 345 | return this.monitor.recordPing(); 346 | case message_types.confirmation: 347 | return this.subscriptions.notify(identifier, "connected"); 348 | case message_types.rejection: 349 | return this.subscriptions.reject(identifier); 350 | default: 351 | return this.subscriptions.notify(identifier, "received", message); 352 | } 353 | }, 354 | open: function() { 355 | ActionCable.log("WebSocket onopen event, using '" + (this.getProtocol()) + "' subprotocol"); 356 | this.disconnected = false; 357 | if (!this.isProtocolSupported()) { 358 | ActionCable.log("Protocol is unsupported. Stopping monitor and disconnecting."); 359 | return this.close({ 360 | allowReconnect: false 361 | }); 362 | } 363 | }, 364 | close: function(event) { 365 | ActionCable.log("WebSocket onclose event"); 366 | if (this.disconnected) { 367 | return; 368 | } 369 | this.disconnected = true; 370 | this.monitor.recordDisconnect(); 371 | return this.subscriptions.notifyAll("disconnected", { 372 | willAttemptReconnect: this.monitor.isRunning() 373 | }); 374 | }, 375 | error: function() { 376 | return ActionCable.log("WebSocket onerror event"); 377 | } 378 | }; 379 | 380 | return Connection; 381 | 382 | })(); 383 | 384 | }).call(this); 385 | (function() { 386 | var slice = [].slice; 387 | 388 | ActionCable.Subscriptions = (function() { 389 | function Subscriptions(consumer) { 390 | this.consumer = consumer; 391 | this.subscriptions = []; 392 | } 393 | 394 | Subscriptions.prototype.create = function(channelName, mixin) { 395 | var channel, params, subscription; 396 | channel = channelName; 397 | params = typeof channel === "object" ? channel : { 398 | channel: channel 399 | }; 400 | subscription = new ActionCable.Subscription(this.consumer, params, mixin); 401 | return this.add(subscription); 402 | }; 403 | 404 | Subscriptions.prototype.add = function(subscription) { 405 | this.subscriptions.push(subscription); 406 | this.consumer.ensureActiveConnection(); 407 | this.notify(subscription, "initialized"); 408 | this.sendCommand(subscription, "subscribe"); 409 | return subscription; 410 | }; 411 | 412 | Subscriptions.prototype.remove = function(subscription) { 413 | this.forget(subscription); 414 | if (!this.findAll(subscription.identifier).length) { 415 | this.sendCommand(subscription, "unsubscribe"); 416 | } 417 | return subscription; 418 | }; 419 | 420 | Subscriptions.prototype.reject = function(identifier) { 421 | var i, len, ref, results, subscription; 422 | ref = this.findAll(identifier); 423 | results = []; 424 | for (i = 0, len = ref.length; i < len; i++) { 425 | subscription = ref[i]; 426 | this.forget(subscription); 427 | this.notify(subscription, "rejected"); 428 | results.push(subscription); 429 | } 430 | return results; 431 | }; 432 | 433 | Subscriptions.prototype.forget = function(subscription) { 434 | var s; 435 | this.subscriptions = (function() { 436 | var i, len, ref, results; 437 | ref = this.subscriptions; 438 | results = []; 439 | for (i = 0, len = ref.length; i < len; i++) { 440 | s = ref[i]; 441 | if (s !== subscription) { 442 | results.push(s); 443 | } 444 | } 445 | return results; 446 | }).call(this); 447 | return subscription; 448 | }; 449 | 450 | Subscriptions.prototype.findAll = function(identifier) { 451 | var i, len, ref, results, s; 452 | ref = this.subscriptions; 453 | results = []; 454 | for (i = 0, len = ref.length; i < len; i++) { 455 | s = ref[i]; 456 | if (s.identifier === identifier) { 457 | results.push(s); 458 | } 459 | } 460 | return results; 461 | }; 462 | 463 | Subscriptions.prototype.reload = function() { 464 | var i, len, ref, results, subscription; 465 | ref = this.subscriptions; 466 | results = []; 467 | for (i = 0, len = ref.length; i < len; i++) { 468 | subscription = ref[i]; 469 | results.push(this.sendCommand(subscription, "subscribe")); 470 | } 471 | return results; 472 | }; 473 | 474 | Subscriptions.prototype.notifyAll = function() { 475 | var args, callbackName, i, len, ref, results, subscription; 476 | callbackName = arguments[0], args = 2 <= arguments.length ? slice.call(arguments, 1) : []; 477 | ref = this.subscriptions; 478 | results = []; 479 | for (i = 0, len = ref.length; i < len; i++) { 480 | subscription = ref[i]; 481 | results.push(this.notify.apply(this, [subscription, callbackName].concat(slice.call(args)))); 482 | } 483 | return results; 484 | }; 485 | 486 | Subscriptions.prototype.notify = function() { 487 | var args, callbackName, i, len, results, subscription, subscriptions; 488 | subscription = arguments[0], callbackName = arguments[1], args = 3 <= arguments.length ? slice.call(arguments, 2) : []; 489 | if (typeof subscription === "string") { 490 | subscriptions = this.findAll(subscription); 491 | } else { 492 | subscriptions = [subscription]; 493 | } 494 | results = []; 495 | for (i = 0, len = subscriptions.length; i < len; i++) { 496 | subscription = subscriptions[i]; 497 | results.push(typeof subscription[callbackName] === "function" ? subscription[callbackName].apply(subscription, args) : void 0); 498 | } 499 | return results; 500 | }; 501 | 502 | Subscriptions.prototype.sendCommand = function(subscription, command) { 503 | var identifier; 504 | identifier = subscription.identifier; 505 | return this.consumer.send({ 506 | command: command, 507 | identifier: identifier 508 | }); 509 | }; 510 | 511 | return Subscriptions; 512 | 513 | })(); 514 | 515 | }).call(this); 516 | (function() { 517 | ActionCable.Subscription = (function() { 518 | var extend; 519 | 520 | function Subscription(consumer, params, mixin) { 521 | this.consumer = consumer; 522 | if (params == null) { 523 | params = {}; 524 | } 525 | this.identifier = JSON.stringify(params); 526 | extend(this, mixin); 527 | } 528 | 529 | Subscription.prototype.perform = function(action, data) { 530 | if (data == null) { 531 | data = {}; 532 | } 533 | data.action = action; 534 | return this.send(data); 535 | }; 536 | 537 | Subscription.prototype.send = function(data) { 538 | return this.consumer.send({ 539 | command: "message", 540 | identifier: this.identifier, 541 | data: JSON.stringify(data) 542 | }); 543 | }; 544 | 545 | Subscription.prototype.unsubscribe = function() { 546 | return this.consumer.subscriptions.remove(this); 547 | }; 548 | 549 | extend = function(object, properties) { 550 | var key, value; 551 | if (properties != null) { 552 | for (key in properties) { 553 | value = properties[key]; 554 | object[key] = value; 555 | } 556 | } 557 | return object; 558 | }; 559 | 560 | return Subscription; 561 | 562 | })(); 563 | 564 | }).call(this); 565 | (function() { 566 | ActionCable.Consumer = (function() { 567 | function Consumer(url, jwt) { 568 | this.url = url; 569 | this.jwt = jwt; 570 | this.subscriptions = new ActionCable.Subscriptions(this); 571 | this.connection = new ActionCable.Connection(this); 572 | } 573 | 574 | Consumer.prototype.send = function(data) { 575 | return this.connection.send(data); 576 | }; 577 | 578 | Consumer.prototype.connect = function() { 579 | return this.connection.open(); 580 | }; 581 | 582 | Consumer.prototype.disconnect = function() { 583 | return this.connection.close({ 584 | allowReconnect: false 585 | }); 586 | }; 587 | 588 | Consumer.prototype.ensureActiveConnection = function() { 589 | if (!this.connection.isActive()) { 590 | return this.connection.open(); 591 | } 592 | }; 593 | 594 | return Consumer; 595 | 596 | })(); 597 | 598 | }).call(this); 599 | }).call(this); 600 | 601 | if (typeof module === "object" && module.exports) { 602 | module.exports = ActionCable; 603 | } else if (typeof define === "function" && define.amd) { 604 | define(ActionCable); 605 | } 606 | }).call(this); 607 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "action-cable-react-jwt", 3 | "version": "0.0.4", 4 | "description": "Rails actioncable integration with JWT Authentication for React and ReactNative", 5 | "main": "action_cable_react_jwt.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/zekedran/action-cable-react-jwt.git" 12 | }, 13 | "keywords": [ 14 | "jwt", 15 | "actioncable", 16 | "react", 17 | "react-native" 18 | ], 19 | "author": "Zeke Dran", 20 | "license": "MIT", 21 | "bugs": { 22 | "url": "https://github.com/zekedran/action-cable-react-jwt/issues" 23 | }, 24 | "homepage": "https://github.com/zekedran/action-cable-react-jwt#readme" 25 | } 26 | --------------------------------------------------------------------------------