Skip to content

Commit

Permalink
Added TokenProvider class
Browse files Browse the repository at this point in the history
  • Loading branch information
alexkonst committed Feb 27, 2023
1 parent 813e12f commit 5d36a98
Show file tree
Hide file tree
Showing 6 changed files with 141 additions and 6 deletions.
9 changes: 6 additions & 3 deletions .semaphore/semaphore.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ blocks:
- name: prepare
commands:
- checkout
- nvm use $(cat .nvmrc) # semaphore does not use node version from .nvmrc for some reason
- nvm install $(cat .nvmrc)
- nvm use
- cache restore node-modules-$SEMAPHORE_GIT_BRANCH-$(checksum package-lock.json),node-modules-$SEMAPHORE_GIT_BRANCH,node-modules-master
- npm ci
- cache store node-modules-$SEMAPHORE_GIT_BRANCH-$(checksum package-lock.json) node_modules
Expand All @@ -23,14 +24,16 @@ blocks:
- name: lint
commands:
- checkout
- nvm use $(cat .nvmrc) # semaphore does not use node version from .nvmrc for some reason
- nvm install $(cat .nvmrc)
- nvm use
- cache restore node-modules-$SEMAPHORE_GIT_BRANCH-$(checksum package-lock.json)
- npm ci
- npm run lint
- name: test
commands:
- checkout
- nvm use $(cat .nvmrc) # semaphore does not use node version from .nvmrc for some reason
- nvm install $(cat .nvmrc)
- nvm use
- cache restore node-modules-$SEMAPHORE_GIT_BRANCH-$(checksum package-lock.json)
- npm ci
- npm test
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@ulms/api-clients",
"version": "5.13.1",
"version": "5.14.0",
"description": "JavaScript API clients for ULMS platform",
"keywords": [],
"homepage": "https://github.com/foxford/ulms-api-clients-js#readme",
Expand Down
23 changes: 23 additions & 0 deletions src/error.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,3 +57,26 @@ export class PresenceError extends Error {
return new PresenceError(errorType)
}
}

export class TokenProviderError extends Error {
constructor(...args) {
super(...args)

this.name = 'TokenProviderError'
}

static get types() {
return {
UNAUTHENTICATED: 'UNAUTHENTICATED',
NETWORK_ERROR: 'NETWORK_ERROR',
UNKNOWN_ERROR: 'UNKNOWN_ERROR',
}
}

static fromType(type) {
const errorType =
TokenProviderError.types[type] || TokenProviderError.types.UNKNOWN_ERROR

return new TokenProviderError(errorType)
}
}
1 change: 1 addition & 0 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,5 @@ export { default as Presence } from './presence'
export { default as PresenceWS } from './presence-ws'
export { default as Telemetry } from './telemetry'
export { default as Tenant } from './tenant'
export { default as TokenProvider } from './token-provider'
export { default as Portal } from './portal'
108 changes: 108 additions & 0 deletions src/token-provider.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
/* eslint-disable camelcase, promise/always-return */
import { makeDeferred } from './common'
import { TokenProviderError } from './error'

class TokenProvider {
constructor(baseUrl, httpClient) {
this.baseUrl = baseUrl
this.context = undefined
this.httpClient = httpClient
this.tokenData = undefined
this.tokenP = undefined
}

setContext(context) {
this.context = context
}

getToken() {
if (this.tokenP) {
return this.tokenP.promise
}

const isTokenDataEmpty = !this.tokenData
const isAccessTokenExpired = isTokenDataEmpty
? false
: Date.now() > this.tokenData.expires_ts

if (isTokenDataEmpty || isAccessTokenExpired) {
this.tokenP = makeDeferred()

this.fetchTokenData()
.then((response) => {
this.updateTokenData(response)
this.resolveAndReset()
})
.catch((error) => {
this.rejectAndReset(error)
})

return this.tokenP.promise
}

return Promise.resolve(this.tokenData.access_token)
}

fetchTokenData() {
const qs = this.context ? `?context=${this.context}` : ''
const url = `${this.baseUrl}/api/user/ulms_token${qs}`

return this.httpClient.post(url, undefined, { credentials: 'include' })
}

rejectAndReset(error) {
/*
* Errors
*
* - unrecoverable (нет смысла повторять запрос)
* 401 {"error":"Авторизуйтесь"} (когда разлогинился в админке или на портале)
* 401 {"error":"Войдите, пожалуйста, чтобы приступить к занятиям."} (когда указан неверный контекст для запроса)
*
* - network error
* error instanceof TypeError && error.message.startsWith('Failed to fetch')
* - отсутствует соединение с сетью
* - ошибка CORS (внезапно ответ 200, но ошибка по заголовкам)
* */
let transformedError

if (
error instanceof TypeError &&
error.message.startsWith('Failed to fetch')
) {
transformedError = TokenProviderError.fromType(
TokenProviderError.types.NETWORK_ERROR
)
} else if (error.error) {
transformedError = TokenProviderError.fromType(
TokenProviderError.types.UNAUTHENTICATED
)
} else {
transformedError = TokenProviderError.fromType(
TokenProviderError.types.UNKNOWN_ERROR
)
}

this.tokenP.reject(transformedError)

this.tokenP = undefined
}

resolveAndReset() {
this.tokenP.resolve(this.tokenData.access_token)

this.tokenP = undefined
}

updateTokenData(updates) {
const { expires_in } = updates
const expires_ts = Date.now() + expires_in * 1e3 - 3e3

this.tokenData = {
...this.tokenData,
...updates,
expires_ts,
}
}
}

export default TokenProvider

0 comments on commit 5d36a98

Please sign in to comment.