Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(nlu): nlu cloud configuration (#5296)
* feat(nlu): nlu cloud configuration (#5246) * feat(nlu): nlu cloud configuration * fix(build): remove bpd * fix(build): fix Docker build * Add workspace workflow * fix(nlu): added module moment & fixed cloud client * fix(bot): don't wait for STUDIO_READY * feat(messaging): messaging cloud configuration (#5261) * first draft * disable waiting for studio Co-authored-by: Simon-Pierre Gingras <892367+spg@users.noreply.github.com> * fix(core): remove STUDIO_READY (#5262) * fix(core): remove STUDIO_READY * Forgot this Co-authored-by: Simon-Pierre Gingras <892367+spg@users.noreply.github.com> Co-authored-by: Laurent Leclerc Poulin <laurentlp@users.noreply.github.com> * fix(nlu): refactor cloud oauth client * chore: upgrade nlu to version 0.1.4 (#5327) Co-authored-by: Simon-Pierre Gingras <892367+spg@users.noreply.github.com> Co-authored-by: Laurent Leclerc Poulin <laurentlp@users.noreply.github.com> Co-authored-by: François Levasseur <francois_levasseur@hotmail.com>
- Loading branch information
1 parent
3e0a0fc
commit 3930383
Showing
13 changed files
with
256 additions
and
16 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
import ms from 'ms' | ||
import { Locker } from './lock' | ||
|
||
export interface Options<T> { | ||
getToken?: (res: T) => string | ||
getExpiryInMs?: (res: T) => number | ||
} | ||
|
||
const defaultExpiry = ms('10m') // 10 minutes in ms | ||
const isTokenActive = (token: string | null, expiration: number | null) => | ||
token && expiration && expiration > Date.now() | ||
|
||
export const cache = <T>(authenticate: () => Promise<T>, options?: Options<T>) => { | ||
const getToken: (res: any) => string = options?.getToken ?? (res => res) | ||
const getExpiry: (res: any) => number = options?.getExpiryInMs ?? (() => defaultExpiry) | ||
|
||
const lock = Locker() | ||
|
||
let token: string | null = null | ||
let expiration: number | null = null | ||
|
||
const tokenCache = async () => { | ||
if (isTokenActive(token, expiration)) { | ||
return token! | ||
} | ||
|
||
const unlock = await lock('cache') | ||
|
||
try { | ||
if (isTokenActive(token, expiration)) { | ||
return token! | ||
} | ||
|
||
const res = await authenticate() | ||
|
||
token = getToken(res) | ||
expiration = Date.now() + getExpiry(res) | ||
return token | ||
} catch (e) { | ||
throw e | ||
} finally { | ||
unlock() | ||
} | ||
} | ||
|
||
tokenCache.reset = () => { | ||
token = null | ||
expiration = null | ||
} | ||
|
||
return tokenCache | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
import { Client } from '@botpress/nlu-client' | ||
import { AxiosInstance } from 'axios' | ||
import { createOauthClient, OauthClientProps } from './oauth' | ||
|
||
export class NLUCloudClient extends Client { | ||
private _client: AxiosInstance | ||
|
||
constructor(options: OauthClientProps) { | ||
super(options.endpoint, undefined) | ||
this._client = createOauthClient(options) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
import { Locker } from './lock' | ||
|
||
describe('getLock', () => { | ||
it('should never release a lock', async () => { | ||
const fn = jest.fn() | ||
|
||
const lock = Locker() | ||
lock('key') | ||
lock('key').then(() => { | ||
fn() | ||
}) | ||
|
||
await new Promise(r => setTimeout(r, 100)) | ||
|
||
expect(fn).not.toHaveBeenCalled() | ||
}) | ||
|
||
it('should wait for lock before updating value', async done => { | ||
const lock = Locker() | ||
|
||
let value = '' | ||
|
||
lock('key').then(unlock => | ||
setTimeout(() => { | ||
value = 'unexpected' | ||
unlock() | ||
}, 100) | ||
) | ||
|
||
expect(value).toEqual('') | ||
|
||
await lock('key') | ||
|
||
expect(value).toEqual('unexpected') | ||
value = 'expected' | ||
expect(value).toEqual('expected') | ||
|
||
setTimeout(() => { | ||
expect(value).toEqual('expected') | ||
done!() | ||
}, 200) | ||
}) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
import { Lock as LockWithCallback } from 'lock' | ||
|
||
type unlockFunc = () => void | ||
|
||
export const Locker = () => { | ||
const lock = LockWithCallback() | ||
|
||
const getLock = async (key: string) => | ||
new Promise<unlockFunc>(resolve => { | ||
lock(key, async unlockFn => { | ||
const unlock = unlockFn() | ||
resolve(unlock) | ||
}) | ||
}) | ||
|
||
return getLock | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
import axios, { AxiosError, AxiosInstance, AxiosRequestConfig } from 'axios' | ||
import { cache } from './cache' | ||
import qs from 'querystring' | ||
|
||
interface OauthTokenClientProps { | ||
oauthUrl: string | ||
clientId: string | ||
clientSecret: string | ||
} | ||
|
||
interface OauthResponse { | ||
access_token: string | ||
expires_in: number | ||
scope: string | ||
token_type: string | ||
} | ||
|
||
const createOauthTokenClient = (axios: AxiosInstance, oauthTokenClientProps: OauthTokenClientProps) => async () => { | ||
const { oauthUrl, clientId, clientSecret } = oauthTokenClientProps | ||
const res = await axios.post( | ||
oauthUrl, | ||
qs.stringify({ client_id: clientId, client_secret: clientSecret, grant_type: 'client_credentials' }) | ||
) | ||
|
||
return res.data as OauthResponse | ||
} | ||
|
||
const requestInterceptor = (authenticate: () => Promise<string>) => async (config: AxiosRequestConfig) => { | ||
const token = await authenticate() | ||
config.headers.Authorization = `Bearer ${token}` | ||
return config | ||
} | ||
|
||
type ErrorRetrier = AxiosError & { config: { _retry: boolean } } | ||
|
||
const errorInterceptor = (instance: AxiosInstance, authenticate: () => Promise<string>) => async ( | ||
error: ErrorRetrier | ||
) => { | ||
if (error.response?.status === 401 && !error.config._retry) { | ||
error.config._retry = true | ||
const token = await authenticate() | ||
const config = error.config | ||
config.headers.Authorization = `Bearer ${token}` | ||
return instance.request(config) | ||
} | ||
|
||
return Promise.reject(error) | ||
} | ||
|
||
export type OauthClientProps = OauthTokenClientProps & { | ||
endpoint: string | ||
} | ||
|
||
export const createOauthClient = ({ clientId, clientSecret, endpoint, oauthUrl }: OauthClientProps) => { | ||
const oauthTokenClient = createOauthTokenClient(axios.create(), { | ||
oauthUrl, | ||
clientId, | ||
clientSecret | ||
}) | ||
|
||
const tokenCache = cache(oauthTokenClient, { | ||
getExpiryInMs: res => res.expires_in * 1000, | ||
getToken: res => res.access_token | ||
}) | ||
|
||
const axiosClient = axios.create({ baseURL: endpoint }) | ||
axiosClient.interceptors.request.use(requestInterceptor(tokenCache)) | ||
axiosClient.interceptors.response.use(undefined, errorInterceptor(axiosClient, tokenCache)) | ||
return axiosClient | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters