Repository: neet/masto.js
Pull request #49 (2019-05-25 04:34:34)

Support Mastodon 2.8.4
- Use `Sec-Websocket-Protocol` to pass token instead of querystring (tootsuite/mastodon#10818)

codecov-io: # [Codecov](https://codecov.io/gh/neet/masto.js/pull/49?src=pr&el=h1) Report
> Merging [#49](https://codecov.io/gh/neet/masto.js/pull/49?src=pr&el=desc) into [master](https://codecov.io/gh/neet/masto.js/commit/c4e0d18480e54c201ec9e12726530bd4d5e686c9?src=pr&el=desc) will **increase** coverage by `2.69%`.
> The diff coverage is `100%`.

[![Impacted file tree graph](https://codecov.io/gh/neet/masto.js/pull/49/graphs/tree.svg?width=650&token=SW9FhwMnZ3&height=150&src=pr)](https://codecov.io/gh/neet/masto.js/pull/49?src=pr&el=tree)

```diff
@@            Coverage Diff             @@
##           master      #49      +/-   ##
==========================================
+ Coverage   97.02%   99.72%   +2.69%     
==========================================
  Files           9        8       -1     
  Lines         370      366       -4     
  Branches       24       20       -4     
==========================================
+ Hits          359      365       +6     
+ Misses         10        1       -9     
+ Partials        1        0       -1
```


| [Impacted Files](https://codecov.io/gh/neet/masto.js/pull/49?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [src/client/gateway.ts](https://codecov.io/gh/neet/masto.js/pull/49/diff?src=pr&el=tree#diff-c3JjL2NsaWVudC9nYXRld2F5LnRz) | `100% <100%> (+1.03%)` | :arrow_up: |
| [src/client/masto-events.ts](https://codecov.io/gh/neet/masto.js/pull/49/diff?src=pr&el=tree#diff-c3JjL2NsaWVudC9tYXN0by1ldmVudHMudHM=) | `95.23% <100%> (ø)` | :arrow_up: |
| [src/index.ts](https://codecov.io/gh/neet/masto.js/pull/49/diff?src=pr&el=tree#diff-c3JjL2luZGV4LnRz) | | |

------

[Continue to review full report at Codecov](https://codecov.io/gh/neet/masto.js/pull/49?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/neet/masto.js/pull/49?src=pr&el=footer). Last update [c4e0d18...6b6a8ef](https://codecov.io/gh/neet/masto.js/pull/49?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).


Patch:
diff --git a/src/client/gateway.ts b/src/client/gateway.ts
index 8f1a9f85..f8c6736c 100644
--- a/src/client/gateway.ts
+++ b/src/client/gateway.ts
@@ -3,6 +3,7 @@ import axios, { AxiosRequestConfig, AxiosResponse } from 'axios';
 import 'isomorphic-form-data';
 import normalizeUrl from 'normalize-url';
 import * as querystring from 'querystring';
+import * as semver from 'semver';
 import { oc } from 'ts-optchain';
 import { MastoNotFoundError } from '../errors/masto-not-found-error';
 import { MastoRateLimitError } from '../errors/masto-rate-limit-error';
@@ -42,7 +43,7 @@ export class Gateway {
   /** Version of the current instance */
   public version = '';
   /** API token of the user */
-  public accessToken = '';
+  public accessToken?: string;
 
   /**
    * @param params Parameters
@@ -314,15 +315,24 @@ export class Gateway {
    * @return Instance of EventEmitter
    */
   public stream(path: string, params: { [key: string]: any } = {}) {
-    if (this.accessToken) {
+    const version = semver.coerce(this.version);
+    const protocols = [];
+
+    // Since v2.8.4, Using `Sec-Websocket-Protocl` to
+    // Pass token string is supported
+    // https://github.com/tootsuite/mastodon/pull/10818
+    if (this.accessToken && version && semver.gte(version, '2.8.4')) {
+      protocols.push(this.accessToken);
+    } else if (this.accessToken) {
       params.access_token = this.accessToken;
     }
 
-    return new MastoEvents().connect(
+    const url =
       this.streamingApiUrl +
-        path +
-        (Object.keys(params).length ? `?${querystring.stringify(params)}` : ''),
-    );
+      path +
+      (Object.keys(params).length ? `?${querystring.stringify(params)}` : '');
+
+    return new MastoEvents().connect(url, protocols);
   }
 
   /**
diff --git a/src/client/masto-events.ts b/src/client/masto-events.ts
index 31b0a2a5..38d5309b 100644
--- a/src/client/masto-events.ts
+++ b/src/client/masto-events.ts
@@ -36,11 +36,12 @@ export class MastoEvents extends EventEmitter {
   /**
    * Connect to the websocket endpoint
    * @param url URL of the websocket endpoint
+   * @param protocols Subprotocol(s) for `Sec-Websocket-Protocol`
    * @param params URL parameters
    */
-  public connect(url: string) {
+  public connect(url: string, protocols?: string | string[]) {
     return new Promise<MastoEvents>((resolve, reject) => {
-      this.ws = new WebSocket(url);
+      this.ws = new WebSocket(url, protocols);
 
       this.ws.addEventListener('message', this.handleMessage);
       this.ws.addEventListener('error', reject);


Test patch:
diff --git a/src/client/__tests__/gateway.spec.ts b/src/client/__tests__/gateway.spec.ts
index 09baa551..e469226b 100644
--- a/src/client/__tests__/gateway.spec.ts
+++ b/src/client/__tests__/gateway.spec.ts
@@ -17,6 +17,7 @@ describe('Gateway', () => {
   beforeEach(() => {
     gateway = new Gateway({
       uri: 'https://example.com',
+      version: '99.9.9',
       streamingApiUrl: 'wss://example.com',
     });
 
@@ -37,12 +38,12 @@ describe('Gateway', () => {
   test('version has been set if construct with version ', () => {
     gateway = new Gateway({
       uri: 'https://example.com',
-      version: '99.9.9',
+      version: '1.2.3',
     });
-    expect(gateway.version).toBe('99.9.9');
+    expect(gateway.version).toBe('1.2.3');
   });
 
-  test('version has been set if construct with accessToken', () => {
+  test('accessToken has been set if construct with accessToken', () => {
     gateway = new Gateway({
       uri: 'https://example.com',
       accessToken: 'token token',
@@ -300,15 +301,23 @@ describe('Gateway', () => {
   test('initialize MastoEvents and call connect with given params', async () => {
     const params = { a: 'a', b: 'b' };
     await gateway.stream('/', params);
-    expect(connectMock).toBeCalledWith('wss://example.com/?a=a&b=b');
+    expect(connectMock).toBeCalledWith('wss://example.com/?a=a&b=b', []);
   });
 
   test('initialize MastoEvents and call connect with access token', async () => {
     gateway.accessToken = 'tokentoken';
     await gateway.stream('/');
+    expect(connectMock).toBeCalledWith('wss://example.com/', ['tokentoken']);
+  });
+
+  test('initialize MastoEvents and call connect with access token as a param for Mastodon < v2.8.4', async () => {
+    gateway.version = '2.8.3';
+    gateway.accessToken = 'tokentoken';
+    await gateway.stream('/');
 
     expect(connectMock).toBeCalledWith(
       'wss://example.com/?access_token=tokentoken',
+      [],
     );
   });
 
diff --git a/src/client/__tests__/masto-events.spec.ts b/src/client/__tests__/masto-events.spec.ts
index 2868fcce..0414b96e 100644
--- a/src/client/__tests__/masto-events.spec.ts
+++ b/src/client/__tests__/masto-events.spec.ts
@@ -36,9 +36,12 @@ describe('MastoEvents', () => {
       }
     });
 
-    await mastoEvents.connect('wss://example.com');
+    await mastoEvents.connect('wss://example.com', []);
 
-    expect((WebSocket as any) as jest.Mock).toBeCalledWith('wss://example.com');
+    expect((WebSocket as any) as jest.Mock).toBeCalledWith(
+      'wss://example.com',
+      [],
+    );
 
     expect(addEventListenerMock).toHaveBeenCalledWith(
       'message',