laravel-echo-api-gateway 0.1.2 → 0.5.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.
@@ -7,12 +7,16 @@ on:
7
7
  jobs:
8
8
  pest:
9
9
  runs-on: ubuntu-latest
10
- name: PestPHP
10
+ strategy:
11
+ fail-fast: true
12
+ matrix:
13
+ php: ['8.1']
14
+ name: Pest - PHP ${{ matrix.php }}
11
15
  steps:
12
- - uses: actions/checkout@v2
16
+ - uses: actions/checkout@v4
13
17
  - uses: shivammathur/setup-php@v2
14
18
  with:
15
- php-version: '8.0'
19
+ php-version: ${{ matrix.php }}
16
20
  extensions: dom, mbstring, soap, bcmath, pdo_sqlite, intl
17
21
  coverage: pcov
18
22
  - uses: ramsey/composer-install@v1
@@ -26,10 +30,10 @@ jobs:
26
30
  AWS_SESSION_TOKEN: fake
27
31
  - name: Upload coverage to Codecov
28
32
  if: always()
29
- uses: codecov/codecov-action@v1
33
+ uses: codecov/codecov-action@v2
30
34
  with:
31
35
  token: ${{ secrets.CODECOV_TOKEN }}
32
- flags: pest
36
+ flags: pest-${{ matrix.php }}
33
37
  file: test-results/pest.xml
34
38
 
35
39
  larastan:
@@ -39,7 +43,7 @@ jobs:
39
43
  - uses: actions/checkout@v2
40
44
  - uses: shivammathur/setup-php@v2
41
45
  with:
42
- php-version: '8.0'
46
+ php-version: ${{ matrix.php }}
43
47
  extensions: soap, bcmath, pdo_sqlite, intl
44
48
  - uses: ramsey/composer-install@v1
45
49
  with:
@@ -76,7 +80,6 @@ jobs:
76
80
  token: ${{ secrets.CODECOV_TOKEN }}
77
81
  flags: jest
78
82
  file: test-results/jest/clover.xml
79
-
80
83
  # phpcs:
81
84
  # runs-on: ubuntu-latest
82
85
  # name: PHP Style Fixer
@@ -10,7 +10,7 @@ jobs:
10
10
  runs-on: ubuntu-latest
11
11
 
12
12
  steps:
13
- - uses: actions/checkout@v2
13
+ - uses: actions/checkout@v4
14
14
  with:
15
15
  ref: ${{ github.event.release.target_commitish }}
16
16
  - name: Use Node.js 12
@@ -24,7 +24,7 @@ jobs:
24
24
  - run: yarn version --new-version ${{ github.event.release.tag_name }} --no-git-tag-version
25
25
  # - run: yarn run build
26
26
  # - run: yarn test
27
- - run: yarn publish --access public --tag latest
27
+ - run: yarn publish --verbose --access public --tag latest
28
28
  env:
29
29
  NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
30
30
  - run: git add package.json
package/README.md CHANGED
@@ -21,7 +21,7 @@ advised to only use this package for non-critical / non-production projects.
21
21
  In order to use this package, your project needs to meet the following criteria:
22
22
 
23
23
  - PHP 7.4 or 8.x
24
- - Laravel 6, 7 or 8
24
+ - Laravel 6 to 11
25
25
  - Uses either [bref](https://bref.sh) or [Laravel Vapor](https://vapor.laravel.com) to deploy to AWS
26
26
  - Has a working queue
27
27
  - Uses Laravel Mix or any other tool to bundle your assets
@@ -120,6 +120,7 @@ provider:
120
120
 
121
121
  environment:
122
122
  # Add these variables
123
+ # Please note : in Laravel 11, this setting is now BROADCAST_CONNECTION
123
124
  BROADCAST_DRIVER: laravel-echo-api-gateway
124
125
  LARAVEL_ECHO_API_GATEWAY_DYNAMODB_TABLE: !Ref ConnectionsTable
125
126
  LARAVEL_ECHO_API_GATEWAY_API_ID: !Ref WebsocketsApi
@@ -196,7 +197,24 @@ window.Echo = new Echo({
196
197
  broadcaster,
197
198
  // replace the placeholders
198
199
  host: 'wss://{api-ip}.execute-api.{region}.amazonaws.com/{stage}',
200
+ authEndpoint: '{auth-url}/broadcasting/auth', // Optional: Use if you have a separate authentication endpoint
201
+ bearerToken: '{token}', // Optional: Use if you need a Bearer Token for authentication
199
202
  });
200
203
  ```
201
204
 
205
+ You can also enable console output by passing a `debug: true` otpion to your window.Echo intializer :
206
+ ```js
207
+ import Echo from 'laravel-echo';
208
+ import {broadcaster} from 'laravel-echo-api-gateway';
209
+
210
+ window.Echo = new Echo({
211
+ broadcaster,
212
+ // replace the placeholders
213
+ host: 'wss://{api-ip}.execute-api.{region}.amazonaws.com/{stage}',
214
+ debug: true
215
+ });
216
+ ```
217
+
218
+
219
+
202
220
  Lastly, you have to generate your assets by running Laravel Mix. After this step, you should be up and running.
@@ -2,6 +2,9 @@ import { Channel } from "./Channel";
2
2
  export declare type Options = {
3
3
  authEndpoint: string;
4
4
  host: string;
5
+ bearerToken: string;
6
+ auth: any;
7
+ debug: boolean;
5
8
  };
6
9
  export declare type MessageBody = {
7
10
  event: string;
@@ -16,7 +19,9 @@ export declare class Websocket {
16
19
  private internalListeners;
17
20
  private channelBacklog;
18
21
  private socketId;
22
+ private closing;
19
23
  private pingInterval;
24
+ private connect;
20
25
  constructor(options: Options);
21
26
  protected parseMessage(body: string): MessageBody;
22
27
  getSocketId(): string;
@@ -21,20 +21,44 @@ function _defineProperties(target, props) {
21
21
  function _createClass(Constructor, protoProps, staticProps) {
22
22
  if (protoProps) _defineProperties(Constructor.prototype, protoProps);
23
23
  if (staticProps) _defineProperties(Constructor, staticProps);
24
+ Object.defineProperty(Constructor, "prototype", {
25
+ writable: false
26
+ });
24
27
  return Constructor;
25
28
  }
26
29
 
30
+ function _extends() {
31
+ _extends = Object.assign || function (target) {
32
+ for (var i = 1; i < arguments.length; i++) {
33
+ var source = arguments[i];
34
+
35
+ for (var key in source) {
36
+ if (Object.prototype.hasOwnProperty.call(source, key)) {
37
+ target[key] = source[key];
38
+ }
39
+ }
40
+ }
41
+
42
+ return target;
43
+ };
44
+
45
+ return _extends.apply(this, arguments);
46
+ }
47
+
27
48
  function _inherits(subClass, superClass) {
28
49
  if (typeof superClass !== "function" && superClass !== null) {
29
50
  throw new TypeError("Super expression must either be null or a function");
30
51
  }
31
52
 
32
- subClass.prototype = Object.create(superClass && superClass.prototype, {
33
- constructor: {
34
- value: subClass,
35
- writable: true,
36
- configurable: true
37
- }
53
+ Object.defineProperty(subClass, "prototype", {
54
+ value: Object.create(superClass && superClass.prototype, {
55
+ constructor: {
56
+ value: subClass,
57
+ writable: true,
58
+ configurable: true
59
+ }
60
+ }),
61
+ writable: false
38
62
  });
39
63
  if (superClass) _setPrototypeOf(subClass, superClass);
40
64
  }
@@ -61,7 +85,7 @@ function _isNativeReflectConstruct() {
61
85
  if (typeof Proxy === "function") return true;
62
86
 
63
87
  try {
64
- Date.prototype.toString.call(Reflect.construct(Date, [], function () {}));
88
+ Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {}));
65
89
  return true;
66
90
  } catch (e) {
67
91
  return false;
@@ -79,6 +103,8 @@ function _assertThisInitialized(self) {
79
103
  function _possibleConstructorReturn(self, call) {
80
104
  if (call && (typeof call === "object" || typeof call === "function")) {
81
105
  return call;
106
+ } else if (call !== void 0) {
107
+ throw new TypeError("Derived constructors may only return object or undefined");
82
108
  }
83
109
 
84
110
  return _assertThisInitialized(self);
@@ -157,83 +183,104 @@ class Connector {
157
183
 
158
184
  var Websocket = /*#__PURE__*/function () {
159
185
  function Websocket(options) {
160
- var _this = this;
161
-
162
186
  _classCallCheck(this, Websocket);
163
187
 
164
188
  this.buffer = [];
165
189
  this.listeners = {};
166
190
  this.internalListeners = {};
167
191
  this.channelBacklog = [];
192
+ this.closing = false;
168
193
  this.options = options;
169
- this.websocket = new WebSocket(options.host);
194
+ this.connect(this.options.host);
195
+ return this;
196
+ }
170
197
 
171
- this.websocket.onopen = function () {
172
- while (_this.buffer.length) {
173
- var message = _this.buffer[0];
198
+ _createClass(Websocket, [{
199
+ key: "connect",
200
+ value: function connect(host) {
201
+ var _this = this;
174
202
 
175
- _this.send(message);
203
+ this.options.debug && console.log('Connecting');
204
+ this.websocket = new WebSocket(host);
176
205
 
177
- _this.buffer.splice(0, 1);
178
- }
179
- };
206
+ this.websocket.onopen = function () {
207
+ _this.send({
208
+ event: 'whoami'
209
+ });
180
210
 
181
- this.websocket.onmessage = function (messageEvent) {
182
- var message = _this.parseMessage(messageEvent.data);
211
+ while (_this.buffer.length) {
212
+ var message = _this.buffer[0];
183
213
 
184
- if (!message) {
185
- return;
186
- }
214
+ _this.send(message);
215
+
216
+ _this.buffer.splice(0, 1);
217
+ }
218
+ };
187
219
 
188
- if (message.channel) {
189
- console.log("Received event ".concat(message.event, " on channel ").concat(message.channel));
220
+ this.websocket.onmessage = function (messageEvent) {
221
+ var message = _this.parseMessage(messageEvent.data);
190
222
 
191
- if (_this.listeners[message.channel] && _this.listeners[message.channel][message.event]) {
192
- _this.listeners[message.channel][message.event](message.data);
223
+ if (!message) {
224
+ return;
193
225
  }
194
226
 
195
- return;
196
- }
227
+ if (message.channel) {
228
+ _this.options.debug && console.log("Received event ".concat(message.event, " on channel ").concat(message.channel));
197
229
 
198
- if (_this.internalListeners[message.event]) {
199
- _this.internalListeners[message.event](message.data);
200
- }
201
- };
230
+ if (_this.listeners[message.channel] && _this.listeners[message.channel][message.event]) {
231
+ _this.listeners[message.channel][message.event](message.data);
232
+ }
202
233
 
203
- this.on('whoami', function (_ref) {
204
- var socketId = _ref.socket_id;
205
- _this.socketId = socketId;
206
- console.log("just set socketId to ".concat(socketId));
234
+ return;
235
+ }
207
236
 
208
- while (_this.channelBacklog.length) {
209
- var channel = _this.channelBacklog[0];
237
+ if (_this.internalListeners[message.event]) {
238
+ _this.internalListeners[message.event](message.data);
239
+ }
240
+ };
210
241
 
211
- _this.actuallySubscribe(channel);
242
+ this.websocket.onclose = function () {
243
+ if (_this.socketId && !_this.closing || !_this.socketId) {
244
+ _this.options.debug && console.info('Connection lost, reconnecting...');
245
+ setTimeout(function () {
246
+ _this.socketId = undefined;
212
247
 
213
- _this.channelBacklog.splice(0, 1);
214
- }
215
- });
216
- this.send({
217
- event: 'whoami'
218
- }); // send ping every 60 seconds to keep connection alive
248
+ _this.connect(host);
249
+ }, 1000);
250
+ }
251
+ };
219
252
 
220
- this.pingInterval = setInterval(function () {
221
- console.log('Sending ping');
253
+ this.on('whoami', function (_ref) {
254
+ var socketId = _ref.socket_id;
255
+ _this.socketId = socketId;
256
+ _this.options.debug && console.log("just set socketId to ".concat(socketId));
222
257
 
223
- _this.send({
224
- event: 'ping'
225
- });
226
- }, 60 * 1000);
227
- return this;
228
- }
258
+ while (_this.channelBacklog.length) {
259
+ var channel = _this.channelBacklog[0];
229
260
 
230
- _createClass(Websocket, [{
261
+ _this.actuallySubscribe(channel);
262
+
263
+ _this.channelBacklog.splice(0, 1);
264
+ }
265
+ }); // send ping every 60 seconds to keep connection alive
266
+
267
+ this.pingInterval = setInterval(function () {
268
+ if (_this.websocket.readyState === _this.websocket.OPEN) {
269
+ _this.options.debug && console.log('Sending ping');
270
+
271
+ _this.send({
272
+ event: 'ping'
273
+ });
274
+ }
275
+ }, 60 * 1000);
276
+ }
277
+ }, {
231
278
  key: "parseMessage",
232
279
  value: function parseMessage(body) {
233
280
  try {
234
281
  return JSON.parse(body);
235
282
  } catch (error) {
236
- console.error(error);
283
+ this.options.debug && console.error(error);
237
284
  return undefined;
238
285
  }
239
286
  }
@@ -260,6 +307,7 @@ var Websocket = /*#__PURE__*/function () {
260
307
  }, {
261
308
  key: "close",
262
309
  value: function close() {
310
+ this.closing = true;
263
311
  this.internalListeners = {};
264
312
  clearInterval(this.pingInterval);
265
313
  this.pingInterval = undefined;
@@ -280,26 +328,32 @@ var Websocket = /*#__PURE__*/function () {
280
328
  var _this2 = this;
281
329
 
282
330
  if (channel.name.startsWith('private-') || channel.name.startsWith('presence-')) {
283
- console.log("Sending auth request for channel ".concat(channel.name));
331
+ this.options.debug && console.log("Sending auth request for channel ".concat(channel.name));
332
+
333
+ if (this.options.bearerToken) {
334
+ this.options.auth.headers['Authorization'] = 'Bearer ' + this.options.bearerToken;
335
+ }
336
+
284
337
  axios.post(this.options.authEndpoint, {
285
338
  socket_id: this.getSocketId(),
286
339
  channel_name: channel.name
340
+ }, {
341
+ headers: this.options.auth.headers || {}
287
342
  }).then(function (response) {
288
- console.log("Subscribing to channels ".concat(channel.name));
343
+ _this2.options.debug && console.log("Subscribing to channels ".concat(channel.name));
289
344
 
290
345
  _this2.send({
291
346
  event: 'subscribe',
292
- data: {
293
- channel: channel.name,
294
- auth: response.data.auth
295
- }
347
+ data: _extends({
348
+ channel: channel.name
349
+ }, response.data)
296
350
  });
297
351
  })["catch"](function (error) {
298
- console.log("Auth request for channel ".concat(channel.name, " failed"));
299
- console.error(error);
352
+ _this2.options.debug && console.log("Auth request for channel ".concat(channel.name, " failed"));
353
+ _this2.options.debug && console.error(error);
300
354
  });
301
355
  } else {
302
- console.log("Subscribing to channels ".concat(channel.name));
356
+ this.options.debug && console.log("Subscribing to channels ".concat(channel.name));
303
357
  this.send({
304
358
  event: 'subscribe',
305
359
  data: {
@@ -508,9 +562,12 @@ var Channel$1 = /*#__PURE__*/function (_BaseChannel) {
508
562
  }, {
509
563
  key: "whisper",
510
564
  value: function whisper(event, data) {
565
+ var channel = this.name;
566
+ var formattedEvent = "client-" + event;
511
567
  this.socket.send({
512
- event: event,
513
- data: data
568
+ "event": formattedEvent,
569
+ data: data,
570
+ channel: channel
514
571
  });
515
572
  return this;
516
573
  }
@@ -20,20 +20,44 @@ var LaravelEchoApiGateway = (function (exports) {
20
20
  function _createClass(Constructor, protoProps, staticProps) {
21
21
  if (protoProps) _defineProperties(Constructor.prototype, protoProps);
22
22
  if (staticProps) _defineProperties(Constructor, staticProps);
23
+ Object.defineProperty(Constructor, "prototype", {
24
+ writable: false
25
+ });
23
26
  return Constructor;
24
27
  }
25
28
 
29
+ function _extends() {
30
+ _extends = Object.assign || function (target) {
31
+ for (var i = 1; i < arguments.length; i++) {
32
+ var source = arguments[i];
33
+
34
+ for (var key in source) {
35
+ if (Object.prototype.hasOwnProperty.call(source, key)) {
36
+ target[key] = source[key];
37
+ }
38
+ }
39
+ }
40
+
41
+ return target;
42
+ };
43
+
44
+ return _extends.apply(this, arguments);
45
+ }
46
+
26
47
  function _inherits(subClass, superClass) {
27
48
  if (typeof superClass !== "function" && superClass !== null) {
28
49
  throw new TypeError("Super expression must either be null or a function");
29
50
  }
30
51
 
31
- subClass.prototype = Object.create(superClass && superClass.prototype, {
32
- constructor: {
33
- value: subClass,
34
- writable: true,
35
- configurable: true
36
- }
52
+ Object.defineProperty(subClass, "prototype", {
53
+ value: Object.create(superClass && superClass.prototype, {
54
+ constructor: {
55
+ value: subClass,
56
+ writable: true,
57
+ configurable: true
58
+ }
59
+ }),
60
+ writable: false
37
61
  });
38
62
  if (superClass) _setPrototypeOf(subClass, superClass);
39
63
  }
@@ -60,7 +84,7 @@ var LaravelEchoApiGateway = (function (exports) {
60
84
  if (typeof Proxy === "function") return true;
61
85
 
62
86
  try {
63
- Date.prototype.toString.call(Reflect.construct(Date, [], function () {}));
87
+ Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {}));
64
88
  return true;
65
89
  } catch (e) {
66
90
  return false;
@@ -78,6 +102,8 @@ var LaravelEchoApiGateway = (function (exports) {
78
102
  function _possibleConstructorReturn(self, call) {
79
103
  if (call && (typeof call === "object" || typeof call === "function")) {
80
104
  return call;
105
+ } else if (call !== void 0) {
106
+ throw new TypeError("Derived constructors may only return object or undefined");
81
107
  }
82
108
 
83
109
  return _assertThisInitialized(self);
@@ -156,83 +182,104 @@ var LaravelEchoApiGateway = (function (exports) {
156
182
 
157
183
  var Websocket = /*#__PURE__*/function () {
158
184
  function Websocket(options) {
159
- var _this = this;
160
-
161
185
  _classCallCheck(this, Websocket);
162
186
 
163
187
  this.buffer = [];
164
188
  this.listeners = {};
165
189
  this.internalListeners = {};
166
190
  this.channelBacklog = [];
191
+ this.closing = false;
167
192
  this.options = options;
168
- this.websocket = new WebSocket(options.host);
193
+ this.connect(this.options.host);
194
+ return this;
195
+ }
169
196
 
170
- this.websocket.onopen = function () {
171
- while (_this.buffer.length) {
172
- var message = _this.buffer[0];
197
+ _createClass(Websocket, [{
198
+ key: "connect",
199
+ value: function connect(host) {
200
+ var _this = this;
173
201
 
174
- _this.send(message);
202
+ this.options.debug && console.log('Connecting');
203
+ this.websocket = new WebSocket(host);
175
204
 
176
- _this.buffer.splice(0, 1);
177
- }
178
- };
205
+ this.websocket.onopen = function () {
206
+ _this.send({
207
+ event: 'whoami'
208
+ });
179
209
 
180
- this.websocket.onmessage = function (messageEvent) {
181
- var message = _this.parseMessage(messageEvent.data);
210
+ while (_this.buffer.length) {
211
+ var message = _this.buffer[0];
182
212
 
183
- if (!message) {
184
- return;
185
- }
213
+ _this.send(message);
214
+
215
+ _this.buffer.splice(0, 1);
216
+ }
217
+ };
186
218
 
187
- if (message.channel) {
188
- console.log("Received event ".concat(message.event, " on channel ").concat(message.channel));
219
+ this.websocket.onmessage = function (messageEvent) {
220
+ var message = _this.parseMessage(messageEvent.data);
189
221
 
190
- if (_this.listeners[message.channel] && _this.listeners[message.channel][message.event]) {
191
- _this.listeners[message.channel][message.event](message.data);
222
+ if (!message) {
223
+ return;
192
224
  }
193
225
 
194
- return;
195
- }
226
+ if (message.channel) {
227
+ _this.options.debug && console.log("Received event ".concat(message.event, " on channel ").concat(message.channel));
196
228
 
197
- if (_this.internalListeners[message.event]) {
198
- _this.internalListeners[message.event](message.data);
199
- }
200
- };
229
+ if (_this.listeners[message.channel] && _this.listeners[message.channel][message.event]) {
230
+ _this.listeners[message.channel][message.event](message.data);
231
+ }
201
232
 
202
- this.on('whoami', function (_ref) {
203
- var socketId = _ref.socket_id;
204
- _this.socketId = socketId;
205
- console.log("just set socketId to ".concat(socketId));
233
+ return;
234
+ }
206
235
 
207
- while (_this.channelBacklog.length) {
208
- var channel = _this.channelBacklog[0];
236
+ if (_this.internalListeners[message.event]) {
237
+ _this.internalListeners[message.event](message.data);
238
+ }
239
+ };
209
240
 
210
- _this.actuallySubscribe(channel);
241
+ this.websocket.onclose = function () {
242
+ if (_this.socketId && !_this.closing || !_this.socketId) {
243
+ _this.options.debug && console.info('Connection lost, reconnecting...');
244
+ setTimeout(function () {
245
+ _this.socketId = undefined;
211
246
 
212
- _this.channelBacklog.splice(0, 1);
213
- }
214
- });
215
- this.send({
216
- event: 'whoami'
217
- }); // send ping every 60 seconds to keep connection alive
247
+ _this.connect(host);
248
+ }, 1000);
249
+ }
250
+ };
218
251
 
219
- this.pingInterval = setInterval(function () {
220
- console.log('Sending ping');
252
+ this.on('whoami', function (_ref) {
253
+ var socketId = _ref.socket_id;
254
+ _this.socketId = socketId;
255
+ _this.options.debug && console.log("just set socketId to ".concat(socketId));
221
256
 
222
- _this.send({
223
- event: 'ping'
224
- });
225
- }, 60 * 1000);
226
- return this;
227
- }
257
+ while (_this.channelBacklog.length) {
258
+ var channel = _this.channelBacklog[0];
228
259
 
229
- _createClass(Websocket, [{
260
+ _this.actuallySubscribe(channel);
261
+
262
+ _this.channelBacklog.splice(0, 1);
263
+ }
264
+ }); // send ping every 60 seconds to keep connection alive
265
+
266
+ this.pingInterval = setInterval(function () {
267
+ if (_this.websocket.readyState === _this.websocket.OPEN) {
268
+ _this.options.debug && console.log('Sending ping');
269
+
270
+ _this.send({
271
+ event: 'ping'
272
+ });
273
+ }
274
+ }, 60 * 1000);
275
+ }
276
+ }, {
230
277
  key: "parseMessage",
231
278
  value: function parseMessage(body) {
232
279
  try {
233
280
  return JSON.parse(body);
234
281
  } catch (error) {
235
- console.error(error);
282
+ this.options.debug && console.error(error);
236
283
  return undefined;
237
284
  }
238
285
  }
@@ -259,6 +306,7 @@ var LaravelEchoApiGateway = (function (exports) {
259
306
  }, {
260
307
  key: "close",
261
308
  value: function close() {
309
+ this.closing = true;
262
310
  this.internalListeners = {};
263
311
  clearInterval(this.pingInterval);
264
312
  this.pingInterval = undefined;
@@ -279,26 +327,32 @@ var LaravelEchoApiGateway = (function (exports) {
279
327
  var _this2 = this;
280
328
 
281
329
  if (channel.name.startsWith('private-') || channel.name.startsWith('presence-')) {
282
- console.log("Sending auth request for channel ".concat(channel.name));
330
+ this.options.debug && console.log("Sending auth request for channel ".concat(channel.name));
331
+
332
+ if (this.options.bearerToken) {
333
+ this.options.auth.headers['Authorization'] = 'Bearer ' + this.options.bearerToken;
334
+ }
335
+
283
336
  axios.post(this.options.authEndpoint, {
284
337
  socket_id: this.getSocketId(),
285
338
  channel_name: channel.name
339
+ }, {
340
+ headers: this.options.auth.headers || {}
286
341
  }).then(function (response) {
287
- console.log("Subscribing to channels ".concat(channel.name));
342
+ _this2.options.debug && console.log("Subscribing to channels ".concat(channel.name));
288
343
 
289
344
  _this2.send({
290
345
  event: 'subscribe',
291
- data: {
292
- channel: channel.name,
293
- auth: response.data.auth
294
- }
346
+ data: _extends({
347
+ channel: channel.name
348
+ }, response.data)
295
349
  });
296
350
  })["catch"](function (error) {
297
- console.log("Auth request for channel ".concat(channel.name, " failed"));
298
- console.error(error);
351
+ _this2.options.debug && console.log("Auth request for channel ".concat(channel.name, " failed"));
352
+ _this2.options.debug && console.error(error);
299
353
  });
300
354
  } else {
301
- console.log("Subscribing to channels ".concat(channel.name));
355
+ this.options.debug && console.log("Subscribing to channels ".concat(channel.name));
302
356
  this.send({
303
357
  event: 'subscribe',
304
358
  data: {
@@ -507,9 +561,12 @@ var LaravelEchoApiGateway = (function (exports) {
507
561
  }, {
508
562
  key: "whisper",
509
563
  value: function whisper(event, data) {
564
+ var channel = this.name;
565
+ var formattedEvent = "client-" + event;
510
566
  this.socket.send({
511
- event: event,
512
- data: data
567
+ "event": formattedEvent,
568
+ data: data,
569
+ channel: channel
513
570
  });
514
571
  return this;
515
572
  }
@@ -17,20 +17,44 @@ function _defineProperties(target, props) {
17
17
  function _createClass(Constructor, protoProps, staticProps) {
18
18
  if (protoProps) _defineProperties(Constructor.prototype, protoProps);
19
19
  if (staticProps) _defineProperties(Constructor, staticProps);
20
+ Object.defineProperty(Constructor, "prototype", {
21
+ writable: false
22
+ });
20
23
  return Constructor;
21
24
  }
22
25
 
26
+ function _extends() {
27
+ _extends = Object.assign || function (target) {
28
+ for (var i = 1; i < arguments.length; i++) {
29
+ var source = arguments[i];
30
+
31
+ for (var key in source) {
32
+ if (Object.prototype.hasOwnProperty.call(source, key)) {
33
+ target[key] = source[key];
34
+ }
35
+ }
36
+ }
37
+
38
+ return target;
39
+ };
40
+
41
+ return _extends.apply(this, arguments);
42
+ }
43
+
23
44
  function _inherits(subClass, superClass) {
24
45
  if (typeof superClass !== "function" && superClass !== null) {
25
46
  throw new TypeError("Super expression must either be null or a function");
26
47
  }
27
48
 
28
- subClass.prototype = Object.create(superClass && superClass.prototype, {
29
- constructor: {
30
- value: subClass,
31
- writable: true,
32
- configurable: true
33
- }
49
+ Object.defineProperty(subClass, "prototype", {
50
+ value: Object.create(superClass && superClass.prototype, {
51
+ constructor: {
52
+ value: subClass,
53
+ writable: true,
54
+ configurable: true
55
+ }
56
+ }),
57
+ writable: false
34
58
  });
35
59
  if (superClass) _setPrototypeOf(subClass, superClass);
36
60
  }
@@ -57,7 +81,7 @@ function _isNativeReflectConstruct() {
57
81
  if (typeof Proxy === "function") return true;
58
82
 
59
83
  try {
60
- Date.prototype.toString.call(Reflect.construct(Date, [], function () {}));
84
+ Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {}));
61
85
  return true;
62
86
  } catch (e) {
63
87
  return false;
@@ -75,6 +99,8 @@ function _assertThisInitialized(self) {
75
99
  function _possibleConstructorReturn(self, call) {
76
100
  if (call && (typeof call === "object" || typeof call === "function")) {
77
101
  return call;
102
+ } else if (call !== void 0) {
103
+ throw new TypeError("Derived constructors may only return object or undefined");
78
104
  }
79
105
 
80
106
  return _assertThisInitialized(self);
@@ -153,83 +179,104 @@ class Connector {
153
179
 
154
180
  var Websocket = /*#__PURE__*/function () {
155
181
  function Websocket(options) {
156
- var _this = this;
157
-
158
182
  _classCallCheck(this, Websocket);
159
183
 
160
184
  this.buffer = [];
161
185
  this.listeners = {};
162
186
  this.internalListeners = {};
163
187
  this.channelBacklog = [];
188
+ this.closing = false;
164
189
  this.options = options;
165
- this.websocket = new WebSocket(options.host);
190
+ this.connect(this.options.host);
191
+ return this;
192
+ }
166
193
 
167
- this.websocket.onopen = function () {
168
- while (_this.buffer.length) {
169
- var message = _this.buffer[0];
194
+ _createClass(Websocket, [{
195
+ key: "connect",
196
+ value: function connect(host) {
197
+ var _this = this;
170
198
 
171
- _this.send(message);
199
+ this.options.debug && console.log('Connecting');
200
+ this.websocket = new WebSocket(host);
172
201
 
173
- _this.buffer.splice(0, 1);
174
- }
175
- };
202
+ this.websocket.onopen = function () {
203
+ _this.send({
204
+ event: 'whoami'
205
+ });
176
206
 
177
- this.websocket.onmessage = function (messageEvent) {
178
- var message = _this.parseMessage(messageEvent.data);
207
+ while (_this.buffer.length) {
208
+ var message = _this.buffer[0];
179
209
 
180
- if (!message) {
181
- return;
182
- }
210
+ _this.send(message);
211
+
212
+ _this.buffer.splice(0, 1);
213
+ }
214
+ };
183
215
 
184
- if (message.channel) {
185
- console.log("Received event ".concat(message.event, " on channel ").concat(message.channel));
216
+ this.websocket.onmessage = function (messageEvent) {
217
+ var message = _this.parseMessage(messageEvent.data);
186
218
 
187
- if (_this.listeners[message.channel] && _this.listeners[message.channel][message.event]) {
188
- _this.listeners[message.channel][message.event](message.data);
219
+ if (!message) {
220
+ return;
189
221
  }
190
222
 
191
- return;
192
- }
223
+ if (message.channel) {
224
+ _this.options.debug && console.log("Received event ".concat(message.event, " on channel ").concat(message.channel));
193
225
 
194
- if (_this.internalListeners[message.event]) {
195
- _this.internalListeners[message.event](message.data);
196
- }
197
- };
226
+ if (_this.listeners[message.channel] && _this.listeners[message.channel][message.event]) {
227
+ _this.listeners[message.channel][message.event](message.data);
228
+ }
198
229
 
199
- this.on('whoami', function (_ref) {
200
- var socketId = _ref.socket_id;
201
- _this.socketId = socketId;
202
- console.log("just set socketId to ".concat(socketId));
230
+ return;
231
+ }
203
232
 
204
- while (_this.channelBacklog.length) {
205
- var channel = _this.channelBacklog[0];
233
+ if (_this.internalListeners[message.event]) {
234
+ _this.internalListeners[message.event](message.data);
235
+ }
236
+ };
206
237
 
207
- _this.actuallySubscribe(channel);
238
+ this.websocket.onclose = function () {
239
+ if (_this.socketId && !_this.closing || !_this.socketId) {
240
+ _this.options.debug && console.info('Connection lost, reconnecting...');
241
+ setTimeout(function () {
242
+ _this.socketId = undefined;
208
243
 
209
- _this.channelBacklog.splice(0, 1);
210
- }
211
- });
212
- this.send({
213
- event: 'whoami'
214
- }); // send ping every 60 seconds to keep connection alive
244
+ _this.connect(host);
245
+ }, 1000);
246
+ }
247
+ };
215
248
 
216
- this.pingInterval = setInterval(function () {
217
- console.log('Sending ping');
249
+ this.on('whoami', function (_ref) {
250
+ var socketId = _ref.socket_id;
251
+ _this.socketId = socketId;
252
+ _this.options.debug && console.log("just set socketId to ".concat(socketId));
218
253
 
219
- _this.send({
220
- event: 'ping'
221
- });
222
- }, 60 * 1000);
223
- return this;
224
- }
254
+ while (_this.channelBacklog.length) {
255
+ var channel = _this.channelBacklog[0];
225
256
 
226
- _createClass(Websocket, [{
257
+ _this.actuallySubscribe(channel);
258
+
259
+ _this.channelBacklog.splice(0, 1);
260
+ }
261
+ }); // send ping every 60 seconds to keep connection alive
262
+
263
+ this.pingInterval = setInterval(function () {
264
+ if (_this.websocket.readyState === _this.websocket.OPEN) {
265
+ _this.options.debug && console.log('Sending ping');
266
+
267
+ _this.send({
268
+ event: 'ping'
269
+ });
270
+ }
271
+ }, 60 * 1000);
272
+ }
273
+ }, {
227
274
  key: "parseMessage",
228
275
  value: function parseMessage(body) {
229
276
  try {
230
277
  return JSON.parse(body);
231
278
  } catch (error) {
232
- console.error(error);
279
+ this.options.debug && console.error(error);
233
280
  return undefined;
234
281
  }
235
282
  }
@@ -256,6 +303,7 @@ var Websocket = /*#__PURE__*/function () {
256
303
  }, {
257
304
  key: "close",
258
305
  value: function close() {
306
+ this.closing = true;
259
307
  this.internalListeners = {};
260
308
  clearInterval(this.pingInterval);
261
309
  this.pingInterval = undefined;
@@ -276,26 +324,32 @@ var Websocket = /*#__PURE__*/function () {
276
324
  var _this2 = this;
277
325
 
278
326
  if (channel.name.startsWith('private-') || channel.name.startsWith('presence-')) {
279
- console.log("Sending auth request for channel ".concat(channel.name));
327
+ this.options.debug && console.log("Sending auth request for channel ".concat(channel.name));
328
+
329
+ if (this.options.bearerToken) {
330
+ this.options.auth.headers['Authorization'] = 'Bearer ' + this.options.bearerToken;
331
+ }
332
+
280
333
  axios.post(this.options.authEndpoint, {
281
334
  socket_id: this.getSocketId(),
282
335
  channel_name: channel.name
336
+ }, {
337
+ headers: this.options.auth.headers || {}
283
338
  }).then(function (response) {
284
- console.log("Subscribing to channels ".concat(channel.name));
339
+ _this2.options.debug && console.log("Subscribing to channels ".concat(channel.name));
285
340
 
286
341
  _this2.send({
287
342
  event: 'subscribe',
288
- data: {
289
- channel: channel.name,
290
- auth: response.data.auth
291
- }
343
+ data: _extends({
344
+ channel: channel.name
345
+ }, response.data)
292
346
  });
293
347
  })["catch"](function (error) {
294
- console.log("Auth request for channel ".concat(channel.name, " failed"));
295
- console.error(error);
348
+ _this2.options.debug && console.log("Auth request for channel ".concat(channel.name, " failed"));
349
+ _this2.options.debug && console.error(error);
296
350
  });
297
351
  } else {
298
- console.log("Subscribing to channels ".concat(channel.name));
352
+ this.options.debug && console.log("Subscribing to channels ".concat(channel.name));
299
353
  this.send({
300
354
  event: 'subscribe',
301
355
  data: {
@@ -504,9 +558,12 @@ var Channel$1 = /*#__PURE__*/function (_BaseChannel) {
504
558
  }, {
505
559
  key: "whisper",
506
560
  value: function whisper(event, data) {
561
+ var channel = this.name;
562
+ var formattedEvent = "client-" + event;
507
563
  this.socket.send({
508
- event: event,
509
- data: data
564
+ "event": formattedEvent,
565
+ data: data,
566
+ channel: channel
510
567
  });
511
568
  return this;
512
569
  }
package/js-src/Channel.ts CHANGED
@@ -1,7 +1,7 @@
1
- import {EventFormatter} from 'laravel-echo/src/util';
2
- import {Channel as BaseChannel} from 'laravel-echo/src/channel/channel';
3
- import {PresenceChannel} from "laravel-echo/src/channel";
4
- import {Websocket} from "./Websocket";
1
+ import { EventFormatter } from 'laravel-echo/src/util';
2
+ import { Channel as BaseChannel } from 'laravel-echo/src/channel/channel';
3
+ import { PresenceChannel } from "laravel-echo/src/channel";
4
+ import { Websocket } from "./Websocket";
5
5
 
6
6
  /**
7
7
  * This class represents a Pusher channel.
@@ -105,9 +105,12 @@ export class Channel extends BaseChannel implements PresenceChannel {
105
105
  }
106
106
 
107
107
  whisper(event: string, data: object): Channel {
108
+ let channel = this.name;
109
+ let formattedEvent = "client-" + event;
108
110
  this.socket.send({
109
- event,
111
+ "event": formattedEvent,
110
112
  data,
113
+ channel,
111
114
  })
112
115
 
113
116
  return this;
@@ -1,7 +1,8 @@
1
- import {Channel} from "./Channel";
2
- import {AxiosResponse} from "axios";
1
+ import { AxiosResponse } from "axios";
2
+ import { Channel } from "./Channel";
3
+
4
+ export type Options = { authEndpoint: string, host: string, bearerToken: string, auth: any, debug: boolean };
3
5
 
4
- export type Options = { authEndpoint: string, host: string };
5
6
  export type MessageBody = { event: string, channel?: string, data: object };
6
7
 
7
8
  export class Websocket {
@@ -19,14 +20,20 @@ export class Websocket {
19
20
 
20
21
  private socketId: string;
21
22
 
23
+ private closing = false;
24
+
22
25
  private pingInterval: NodeJS.Timeout;
23
26
 
24
- constructor(options: Options) {
25
- this.options = options;
27
+ private connect(host: string): void {
28
+ this.options.debug && console.log('Connecting');
26
29
 
27
- this.websocket = new WebSocket(options.host)
30
+ this.websocket = new WebSocket(host)
28
31
 
29
32
  this.websocket.onopen = () => {
33
+ this.send({
34
+ event: 'whoami',
35
+ })
36
+
30
37
  while (this.buffer.length) {
31
38
  const message = this.buffer[0]
32
39
 
@@ -44,7 +51,7 @@ export class Websocket {
44
51
  }
45
52
 
46
53
  if (message.channel) {
47
- console.log(`Received event ${message.event} on channel ${message.channel}`)
54
+ this.options.debug && console.log(`Received event ${message.event} on channel ${message.channel}`)
48
55
 
49
56
  if (this.listeners[message.channel] && this.listeners[message.channel][message.event]) {
50
57
  this.listeners[message.channel][message.event](message.data)
@@ -56,12 +63,24 @@ export class Websocket {
56
63
  if (this.internalListeners[message.event]) {
57
64
  this.internalListeners[message.event](message.data)
58
65
  }
66
+
59
67
  }
60
68
 
69
+
70
+ this.websocket.onclose = () => {
71
+ if (this.socketId && !this.closing || !this.socketId) {
72
+ this.options.debug && console.info('Connection lost, reconnecting...');
73
+ setTimeout(() => {
74
+ this.socketId = undefined
75
+ this.connect(host)
76
+ }, 1000);
77
+ }
78
+ };
79
+
61
80
  this.on('whoami', ({ socket_id: socketId }) => {
62
81
  this.socketId = socketId
63
82
 
64
- console.log(`just set socketId to ${socketId}`)
83
+ this.options.debug && console.log(`just set socketId to ${socketId}`)
65
84
 
66
85
  while (this.channelBacklog.length) {
67
86
  const channel = this.channelBacklog[0]
@@ -72,19 +91,24 @@ export class Websocket {
72
91
  }
73
92
  })
74
93
 
75
- this.send({
76
- event: 'whoami',
77
- })
78
94
 
79
95
  // send ping every 60 seconds to keep connection alive
80
96
  this.pingInterval = setInterval(() => {
81
- console.log('Sending ping')
82
-
83
- this.send({
84
- event: 'ping',
85
- })
97
+ if (this.websocket.readyState === this.websocket.OPEN) {
98
+ this.options.debug && console.log('Sending ping')
99
+ this.send({
100
+ event: 'ping',
101
+ })
102
+ }
86
103
  }, 60 * 1000)
87
104
 
105
+ }
106
+
107
+ constructor(options: Options) {
108
+ this.options = options;
109
+
110
+ this.connect(this.options.host);
111
+
88
112
  return this
89
113
  }
90
114
 
@@ -92,7 +116,7 @@ export class Websocket {
92
116
  try {
93
117
  return JSON.parse(body)
94
118
  } catch (error) {
95
- console.error(error)
119
+ this.options.debug && console.error(error)
96
120
 
97
121
  return undefined
98
122
  }
@@ -115,7 +139,8 @@ export class Websocket {
115
139
  this.buffer.push(message)
116
140
  }
117
141
 
118
- close (): void {
142
+ close(): void {
143
+ this.closing = true
119
144
  this.internalListeners = {}
120
145
 
121
146
  clearInterval(this.pingInterval)
@@ -134,27 +159,33 @@ export class Websocket {
134
159
 
135
160
  private actuallySubscribe(channel: Channel): void {
136
161
  if (channel.name.startsWith('private-') || channel.name.startsWith('presence-')) {
137
- console.log(`Sending auth request for channel ${channel.name}`)
162
+ this.options.debug && console.log(`Sending auth request for channel ${channel.name}`)
163
+
164
+ if (this.options.bearerToken) {
165
+ this.options.auth.headers['Authorization'] = 'Bearer ' + this.options.bearerToken;
166
+ }
138
167
 
139
168
  axios.post(this.options.authEndpoint, {
140
169
  socket_id: this.getSocketId(),
141
170
  channel_name: channel.name,
171
+ }, {
172
+ headers: this.options.auth.headers || {}
142
173
  }).then((response: AxiosResponse) => {
143
- console.log(`Subscribing to channels ${channel.name}`)
174
+ this.options.debug && console.log(`Subscribing to channels ${channel.name}`)
144
175
 
145
176
  this.send({
146
177
  event: 'subscribe',
147
178
  data: {
148
179
  channel: channel.name,
149
- auth: response.data.auth,
180
+ ...response.data
150
181
  },
151
182
  })
152
183
  }).catch((error) => {
153
- console.log(`Auth request for channel ${channel.name} failed`)
154
- console.error(error)
184
+ this.options.debug && console.log(`Auth request for channel ${channel.name} failed`)
185
+ this.options.debug && console.error(error)
155
186
  })
156
187
  } else {
157
- console.log(`Subscribing to channels ${channel.name}`)
188
+ this.options.debug && console.log(`Subscribing to channels ${channel.name}`)
158
189
 
159
190
  this.send({
160
191
  event: 'subscribe',
@@ -1,12 +1,15 @@
1
1
  import WS from "jest-websocket-mock";
2
- import {Connector} from "../js-src/Connector";
3
- import {Channel} from "../js-src/Channel";
2
+ import { Connector } from "../js-src/Connector";
3
+ import { Channel } from "../js-src/Channel";
4
+
5
+ const mockedHost = 'ws://localhost:1234';
4
6
 
5
7
  describe('Connector', () => {
6
8
  let server: WS;
7
9
 
8
10
  beforeEach(() => {
9
- server = new WS("ws://localhost:1234");
11
+ jest.useRealTimers();
12
+ server = new WS(mockedHost);
10
13
  });
11
14
 
12
15
  afterEach(() => {
@@ -15,7 +18,7 @@ describe('Connector', () => {
15
18
 
16
19
  test('socket id is correctly set', async () => {
17
20
  const connector = new Connector({
18
- host: "ws://localhost:1234",
21
+ host: mockedHost,
19
22
  })
20
23
 
21
24
  await server.connected;
@@ -26,9 +29,29 @@ describe('Connector', () => {
26
29
  expect(connector.socketId()).toBe('test-socket-id')
27
30
  })
28
31
 
32
+ test('we reconnect to the server on error', async () => {
33
+ const connector = new Connector({
34
+ host: mockedHost,
35
+ })
36
+
37
+ await server.connected;
38
+ await expect(server).toReceiveMessage('{"event":"whoami"}');
39
+ server.send('{"event":"whoami","data":{"socket_id":"test-socket-id"}}')
40
+
41
+ server.close();
42
+ await server.closed;
43
+ server.server.stop(() => (server = new WS(mockedHost)));
44
+
45
+ await server.connected;
46
+ await expect(server).toReceiveMessage('{"event":"whoami"}');
47
+ server.send('{"event":"whoami","data":{"socket_id":"test-socket-id2"}}')
48
+
49
+ expect(connector.socketId()).toBe('test-socket-id2')
50
+ })
51
+
29
52
  test('we can subscribe to a channel and listen to events', async () => {
30
53
  const connector = new Connector({
31
- host: "ws://localhost:1234",
54
+ host: mockedHost,
32
55
  })
33
56
 
34
57
  await server.connected;
@@ -54,4 +77,33 @@ describe('Connector', () => {
54
77
  expect(handler1).toBeCalled();
55
78
  expect(handler2).not.toBeCalled();
56
79
  })
80
+
81
+ test('we can send a whisper event', async () => {
82
+ const connector = new Connector({
83
+ host: mockedHost,
84
+ })
85
+
86
+ await server.connected;
87
+
88
+ await expect(server).toReceiveMessage('{"event":"whoami"}');
89
+ server.send('{"event":"whoami","data":{"socket_id":"test-socket-id"}}')
90
+
91
+ const channel = connector.channel('my-test-channel')
92
+
93
+ await expect(server).toReceiveMessage('{"event":"subscribe","data":{"channel":"my-test-channel"}}');
94
+
95
+ server.send('{"event":"subscription_succeeded","channel":"my-test-channel"}')
96
+
97
+ expect(channel).toBeInstanceOf(Channel)
98
+
99
+ const handler1 = jest.fn();
100
+ const handler2 = jest.fn();
101
+
102
+ channel.on('client-whisper', handler1)
103
+
104
+ server.send('{"event":"client-whisper","data":"whisper","channel":"my-test-channel","data":{}}')
105
+
106
+ expect(handler1).toBeCalled();
107
+ expect(handler2).not.toBeCalled();
108
+ })
57
109
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "laravel-echo-api-gateway",
3
- "version": "0.1.2",
3
+ "version": "0.5.1",
4
4
  "description": "Use Laravel Echo with API Gateway Websockets",
5
5
  "keywords": [
6
6
  "laravel",
package/phpstan.neon.dist CHANGED
@@ -3,6 +3,8 @@ includes:
3
3
 
4
4
  parameters:
5
5
 
6
+ phpVersion: 80399
7
+
6
8
  paths:
7
9
  - src
8
10
  - tests