Pull to refresh

Установка токена в запросы Axios асинхронно

Я не являюсь профессиональным программистом. Но так как моя компания маленькая, то приходится заниматься много чем, в том числе и написанием кода. Здесь буду выкладывать свои решения, которые оказались работающими и, возможно, будут полезны таким же как я.

Здесь используются: Express.js, TypeScript, Axios, Mongoose.

В рамках автоматизации некоторых процессов работы с сервисом Мой Склад (moyskald.ru) потребовалось написать блок для работы с их API. Сервис позволяет получать и обновлять токен, который потом нужно добавлять в заголовки всех запросов.

Первоначальный вариант

Первоначально полученный token хранился в файле конфета и код создания инстанса выглядел так:

// moyskladHttp.ts

import axios from 'axios'
import * as moySkaldConfig from '../../../config/moySklad'

const moyskladHttp = axios.create({
    baseURL: moySkaldConfig.baseUrl,
    headers: {
        'Authorization': `Bearer ${moySkaldConfig.token}`
    },
    validateStatus: () => true
})

export default moyskladHttp

В этом варианте не устраивала потенциальная возможность смены токена. Пришлось бы править код и заново разворачивать все на хостинге. Поэтому было решено хранить токен в базе данных (используется  Mongo). Здесь его можно оперативно изменить напрямую или через через веб-интерфейс.

Исправленный вариант

Простой вариант - создать инстанс без заголовка авторизации и добавлять его уже при каждом запросе. Выглядит это так:

const moyskladHttp = axios.create({
    baseURL: moySkaldConfig.baseUrl,
    validateStatus: () => true
})

moyskladHttp.interceptors.request.use(async (config) => {
    const token =  await getToken()
    return {
        ...config,
        headers: { ...config.headers, Authorization: `Bearer ${token}` }
    }
})

функция getToken() делает асинхронный запрос к нашей базе и возвращает токен. Она выглядит так:

const getToken = async () => {
    const doc = await Credential.findOne({ key: 'moyskladToken' })
    return doc?.value || ''
}

Credential - это модель mongoose для нашей базы. Токены хранятся в коллекции ‘credentials’ и документы имеют вид: {key: string, value: string}

Имеющееся решение плохо тем, что возникает дополнительная задержка при каждом запросе к API сервиса Мой Склад. Так как нужно получить сначала токен из базы. С одной стороны, специфика наших задач не требует максимальной скорости работы сервиса. То есть проблема совсем не критичная. С другой, решение вопроса довольно простое.

Для этого обернем функцию getToken() вспомогательной функцией _getToken(), которая будет хранить единожды полученный токен. Выглядит она так:

const _getToken = () => {
    let token = ''

    return async () => {
        if (token !== '') return token
        const doc = await Credential.findOne({ key: 'moyskladToken' })
        token = doc?.value || ''
        return token
    }
}

const getToken = _getToken()

Переменная token доступна хранится в функции _getToken() и доступна при этом внутри нашей getToken(). Здесь дополнительно производится проверка: если токен еще не задан, то вытягиваем его из базы. Если получилось, то записываем его в переменную token (сам токен или пустую строку, в случае какой-либо ошибки). Таким образом, единожды полученный токен будет выдаваться сразу, минуя обращение к базе.

Целиком код выглядит так:

import axios from 'axios'
import * as moySkaldConfig from '../../../config/moySklad'
import Credential from '../../../models/credential.models'

const _getToken = () => {

    let token = ''

    return async () => {
        if (token !== '') return token
        const doc = await Credential.findOne({ key: 'moyskladToken' })
        token = doc?.value || ''
        return token
    }
}
const getToken = _getToken()

const moyskladHttp = axios.create({
    baseURL: moySkaldConfig.baseUrl,
    validateStatus: () => true
})


moyskladHttp.interceptors.request.use(async (config) => {
    const token =  await getToken()
    return {
        ...config,
        headers: { ...config.headers, Authorization: `Bearer ${token}`}
    }
})

export default moyskladHttp

Tags:
Hubs:
You can’t comment this publication because its author is not yet a full member of the community. You will be able to contact the author only after he or she has been invited by someone in the community. Until then, author’s username will be hidden by an alias.