Skip to content

Commit

Permalink
Merge pull request #174 from Kocal/feat/v2
Browse files Browse the repository at this point in the history
  • Loading branch information
Kocal authored May 21, 2020
2 parents a7ec39e + 81d06d1 commit ac08e50
Show file tree
Hide file tree
Showing 22 changed files with 7,433 additions and 4,614 deletions.
24 changes: 24 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
on:
push:
branches:
- master

name: Release

jobs:
build:
name: Create Release
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2

- name: Install Node.js
uses: actions/setup-node@v1
with:
node-version: 12.x

- run: yarn install --frozen-lockfile

- run: yarn semantic-release
env:
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
39 changes: 39 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
name: Test

on:
pull_request:
branches:
- '*'

jobs:
build:
runs-on: ubuntu-latest

strategy:
matrix:
node-version: [10.x, 12.x, 13.x]

steps:
- uses: actions/checkout@v2

- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v1
with:
node-version: ${{ matrix.node-version }}

- name: Get yarn cache directory
id: yarn-cache
run: echo "::set-output name=dir::$(yarn cache dir)"

- name: Restore yarn cache (if available)
uses: actions/cache@v1
with:
path: ${{ steps.yarn-cache.outputs.dir }}
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
${{ runner.os }}-yarn-
- run: yarn install --frozen-lockfile
- run: yarn lint
- run: yarn build
- run: yarn test -i && yarn codecov
3 changes: 1 addition & 2 deletions .prettierrc
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
{
"singleQuote": true,
"printWidth": 120,
"trailingComma": "es5"
"printWidth": 120
}
34 changes: 0 additions & 34 deletions .travis.yml

This file was deleted.

59 changes: 48 additions & 11 deletions docs/other/twitch.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,30 +6,67 @@ Methods for the [New Twitch API](https://dev.twitch.tv/docs/api/).
You first need to create a [Twitch application](https://glass.twitch.tv/console/apps)
:::

## Register API keys
## Register API key

You need to register an API key to use the Twitch API.

```typescript
import { registerTwitchApiKeys } from '@kocal/web-extension-library';
import { registerTwitchApiKey } from '@kocal/web-extension-library';

// If you are building an extension for a lot of users (~4000 or more),
// it's advised to create multiple Twitch application to prevent API calls limitations
registerTwitchApiKeys(['<your api key>', '<your second api key if needed>']);
registerTwitchApiKey('<your api key>');
```

## Picking an API key

Returns _randomly_ an API key.
It throws an error if you forgot to call [`registerTwitchApiKeys`](#register-api-keys).
### Access the API key

:::warning
Normally you don't have to call this method by yourself,
this is an internal method used by [`getTwitchGame`](#getting-information-on-a-game) or [`getTwitchLiveStreams`](#getting-live-streams).
:::

Returns the API key.
It throws an error if you forgot to call [`registerTwitchApiKey`](#register-api-keys).


```typescript
import { pickTwitchApiKey } from '@kocal/web-extension-library';
import { getTwitchApiKey } from '@kocal/web-extension-library';

getTwitchApiKey();
```

## OAuth

:::tip
You need to update your extension permissions, edit your `manifest.json` with:

```json
{
"permissions": ["identity"]
}
```

You also need to configure the `redirect_uri` in your Twitch Application.
:::

Twitch now requires to [be authenticated](https://dev.twitch.tv/docs/authentication) for each requests.

The function `askTwitchAccessToken` will take cares of:
- trigger the web auth flow to loggin on Twitch
- validate the access token
- handle expiration
- read and write to sync storage, to prevent unecessary Twitch API calls

```js
import { askTwitchAccessToken } from '@kocal/web-extension-library';

const accessToken = await askTwitchAccessToken();
```

Then you need to register it back to the library:

```js
import { registerTwitchAccessToken } from '@kocal/web-extension-library';

pickTwitchApiKey();
registerTwitchAccessToken(accessToken);
```

## Getting information on a Game
Expand Down
3 changes: 2 additions & 1 deletion jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@

module.exports = {
setupFiles: [
'jest-webextension-mock'
'jest-webextension-mock',
'jest-date-mock'
],
transform: {
'^.+\\.tsx?$': 'ts-jest',
Expand Down
42 changes: 23 additions & 19 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@
"repository": "https://github.com/Kocal-Web-Extensions/library.git",
"author": "Hugo Alliaume <[email protected]>",
"license": "MIT",
"engines": {
"node": "^10.13.0 || >=12.0.0"
},
"main": "dist/lib/index.js",
"files": [
"dist"
Expand All @@ -27,37 +30,38 @@
"lint-staged": {
"*.ts": [
"yarn lint --fix",
"prettier --write",
"git add"
"prettier --write"
]
},
"publishConfig": {
"access": "public"
},
"devDependencies": {
"@kocal/semantic-release-preset": "^1.1.0",
"@types/jest": "^23.3.2",
"@types/node": "^10.9.4",
"@types/qs": "^6.5.1",
"axios-mock-adapter": "^1.15.0",
"husky": "^1.2.0",
"jest": "^23.6.0",
"jest-webextension-mock": "^3.4.0",
"lint-staged": "^8.1.0",
"prettier": "^1.14.2",
"rimraf": "^2.6.2",
"semantic-release": "^15.12.3",
"ts-jest": "^23.1.4",
"@kocal/semantic-release-preset": "^2.0.2",
"@types/jest": "^25.2.3",
"@types/node": "^10.17.24",
"@types/qs": "^6.9.3",
"axios-mock-adapter": "^1.18.1",
"codecov": "^3.7.0",
"husky": "^4.2.5",
"jest": "^26.0.1",
"jest-date-mock": "^1.0.8",
"jest-webextension-mock": "^3.5.4",
"lint-staged": "^10.2.4",
"prettier": "^2.0.5",
"rimraf": "^3.0.2",
"semantic-release": "^17.0.7",
"ts-jest": "^26.0.0",
"tslint": "^5.11.0",
"tslint-config-airbnb": "^5.11.0",
"tslint-config-prettier": "^1.15.0",
"typescript": "^3.0.3",
"vuepress": "^0.14.4",
"web-ext-types": "^2.1.0"
"vuepress": "^1.5.0",
"web-ext-types": "^3.2.1"
},
"dependencies": {
"axios": "^0.18.0",
"qs": "^6.5.2",
"webextension-polyfill": "^0.3.1"
"qs": "^6.9.3",
"webextension-polyfill": "^0.6.0"
}
}
1 change: 0 additions & 1 deletion src/twitch/api-keys.ts

This file was deleted.

131 changes: 131 additions & 0 deletions src/twitch/authorization.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
import { advanceBy as advanceDateBy, advanceTo as advanceDateTo, clear as clearDate } from 'jest-date-mock';
import axios from 'axios';
import axiosMockAdapter from 'axios-mock-adapter';
import { askTwitchAccessToken, registerTwitchApiKey } from '.';

// @ts-ignore
browser.identity = browser.identity || {};
// @ts-ignore
browser.identity.getRedirectURL = jest.fn((path) => {
return `http://localhost/${path}`;
});
// @ts-ignore
browser.identity.launchWebAuthFlow = jest.fn((path) => {
return `https://localhost#access_token=0123456789abcdefghijABCDEFGHIJ&scope=viewing_activity_read&state=c3ab8aa609ea11e793ae92361f002671&token_type=bearer`;
});

describe('Twitch Authorization', () => {
const axiosMock = new axiosMockAdapter(axios);

beforeEach(() => {
clearDate();
axiosMock.reset();
browser.storage.sync.clear();
// @ts-ignore
browser.storage.sync.get.mockClear();
// @ts-ignore
browser.storage.sync.set.mockClear();
});

test('Read from sync storage', async () => {
browser.storage.sync.set({
twitchAuthorization: {
accessToken: 'abc',
client_id: 'def',
expirationTimestamp: new Date().getTime() + 60 * 1000,
expires_in: 60,
login: 'thekocal',
scopes: [],
user_id: '11111111',
},
});
// @ts-ignore
browser.storage.sync.set.mockClear();

const accessToken = await askTwitchAccessToken();

expect(browser.storage.sync.get).toHaveBeenNthCalledWith(1, ['twitchAuthorization']);
expect(browser.storage.sync.set).not.toHaveBeenCalled();

expect(accessToken).toEqual('abc');
});

test('Ask for token if sync storage is empty', async () => {
advanceDateTo(1590021266683);

axiosMock.onGet(`https://id.twitch.tv/oauth2/validate`).reply(200, {
client_id: 'def',
expires_in: 60,
login: 'thekocal',
scopes: [],
user_id: '11111111',
});

registerTwitchApiKey('api-key');

const accessToken = await askTwitchAccessToken();

expect(browser.storage.sync.get).toHaveBeenNthCalledWith(1, ['twitchAuthorization']);
expect(browser.storage.sync.set).toHaveBeenNthCalledWith(1, {
twitchAuthorization: {
accessToken: '0123456789abcdefghijABCDEFGHIJ',
expirationTimestamp: new Date().getTime() + 60 * 1000,
client_id: 'def',
expires_in: 60,
login: 'thekocal',
scopes: [],
user_id: '11111111',
},
});

expect(accessToken).toEqual('0123456789abcdefghijABCDEFGHIJ');
});

test('Renew if expired', async () => {
const expiresIn = 1; // 1 sec

advanceDateTo(1590021266683);

axiosMock.onGet(`https://id.twitch.tv/oauth2/validate`).reply(200, {
client_id: 'def',
expires_in: 60,
login: 'thekocal',
scopes: [],
user_id: '11111111',
});

registerTwitchApiKey('api-key');

browser.storage.sync.set({
twitchAuthorization: {
accessToken: 'abc',
client_id: 'def',
expirationTimestamp: new Date().getTime(),
expires_in: expiresIn,
login: 'thekocal',
scopes: [],
user_id: '11111111',
},
});
// @ts-ignore
browser.storage.sync.set.mockClear();

advanceDateBy(expiresIn * 2 * 1000);
const accessToken = await askTwitchAccessToken();

expect(browser.storage.sync.get).toHaveBeenNthCalledWith(1, ['twitchAuthorization']);
expect(browser.storage.sync.set).toHaveBeenNthCalledWith(1, {
twitchAuthorization: {
accessToken: '0123456789abcdefghijABCDEFGHIJ',
expirationTimestamp: new Date().getTime() + 60 * 1000,
client_id: 'def',
expires_in: 60,
login: 'thekocal',
scopes: [],
user_id: '11111111',
},
});

expect(accessToken).toEqual('0123456789abcdefghijABCDEFGHIJ');
});
});
Loading

0 comments on commit ac08e50

Please sign in to comment.