Skip to content

Commit

Permalink
feat(byods): create the main BYODS class implementation (#3824)
Browse files Browse the repository at this point in the history
  • Loading branch information
bhabalan authored Sep 18, 2024
1 parent 0c1a758 commit 8f30732
Show file tree
Hide file tree
Showing 21 changed files with 1,327 additions and 131 deletions.
4 changes: 2 additions & 2 deletions packages/byods/jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ const jestConfig = {
rootDir: './',
setupFilesAfterEnv: ['<rootDir>/jest.setup.js'],
testEnvironment: 'node',
testMatch: ['<rootDir>/**/*.test.ts'],
transformIgnorePatterns: ['/node_modules/(?!node-fetch)|data-uri-to-buffer'],
testMatch: ['<rootDir>/test/unit/spec/**/*.ts'],
transformIgnorePatterns: [],
testPathIgnorePatterns: ['/node_modules/', '/dist/'],
testResultsProcessor: 'jest-junit',
// Clear mocks in between tests by default
Expand Down
10 changes: 5 additions & 5 deletions packages/byods/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@
"@typescript-eslint/eslint-plugin": "5.38.1",
"@typescript-eslint/parser": "5.38.1",
"@web/dev-server": "0.4.5",
"@webex/jest-config-legacy": "workspace:*",
"@webex/legacy-tools": "workspace:*",
"chai": "4.3.4",
"cspell": "5.19.2",
"esbuild": "^0.17.19",
Expand Down Expand Up @@ -114,11 +116,9 @@
"noprompt": true
},
"dependencies": {
"@types/node-fetch": "^2.6.11",
"@webex/jest-config-legacy": "workspace:*",
"@webex/legacy-tools": "workspace:*",
"@webex/media-helpers": "workspace:*",
"node-fetch": "^3.3.2"
"@types/node-fetch": "2.6.11",
"jose": "5.8.0",
"node-fetch": "3.3.2"
},
"type": "module"
}
46 changes: 0 additions & 46 deletions packages/byods/src/BYODS.ts

This file was deleted.

32 changes: 0 additions & 32 deletions packages/byods/src/BYoDS.test.ts

This file was deleted.

28 changes: 0 additions & 28 deletions packages/byods/src/apiClient.ts

This file was deleted.

201 changes: 201 additions & 0 deletions packages/byods/src/base-client/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,201 @@
import fetch, {Response, RequestInit} from 'node-fetch';

import TokenManager from '../token-manager';
import DataSourceClient from '../data-source-client';
import {HttpClient, ApiResponse} from '../http-client/types';

export default class BaseClient {
private baseUrl: string;
private headers: Record<string, string>;
private tokenManager: TokenManager;
private orgId: string;

public dataSource: DataSourceClient;

/**
* Creates an instance of BaseClient.
* @param {string} baseUrl - The base URL for the API.
* @param {Record<string, string>} headers - The additional headers to be used in requests.
* @param {TokenManager} tokenManager - The token manager instance.
* @param {string} orgId - The organization ID.
* @example
* const client = new BaseClient('https://webexapis.com/v1', { 'Your-Custom-Header': 'some value' }, tokenManager, 'org123');
*/
constructor(
baseUrl: string,
headers: Record<string, string>,
tokenManager: TokenManager,
orgId: string
) {
this.baseUrl = baseUrl;
this.headers = headers;
this.tokenManager = tokenManager;
this.orgId = orgId;
this.dataSource = new DataSourceClient(this.getHttpClientForOrg());
}

/**
* Makes an HTTP request.
* @param {string} endpoint - The API endpoint.
* @param {RequestInit} [options=\{\}] - The request options.
* @returns {Promise<ApiResponse<T>>} - The API response.
* @template T
* @example
* const response = await client.request('/endpoint', { method: 'GET', headers: {} });
*/
public async request<T>(endpoint: string, options: RequestInit = {}): Promise<ApiResponse<T>> {
const url = `${this.baseUrl}${endpoint}`;
const token = await this.getToken();

const response: Response = await fetch(url, {
...options,
headers: {
Authorization: `Bearer ${token}`,
...this.headers,
...options.headers,
},
});

const data: any = await response.json();
if (!response.ok) {
throw new Error(`Error: ${response.status} - ${data.message}`);
}

return {data, status: response.status};
}

/**
* Makes a POST request.
* @param {string} endpoint - The API endpoint.
* @param {Record<string, any>} body - The request body.
* @param {Record<string, string>} [headers=\{\}] - The request headers.
* @returns {Promise<ApiResponse<T>>} - The API response.
* @template T
* @example
* const response = await client.post('/endpoint', { key: 'value' });
*/
public async post<T>(
endpoint: string,
body: Record<string, any>,
headers: Record<string, string> = {}
): Promise<ApiResponse<T>> {
return this.request<T>(endpoint, {
method: 'POST',
body: JSON.stringify(body),
headers: {'Content-Type': 'application/json', ...headers},
});
}

/**
* Makes a PUT request.
* @param {string} endpoint - The API endpoint.
* @param {Record<string, any>} body - The request body.
* @returns {Promise<ApiResponse<T>>} - The API response.
* @template T
* @example
* const response = await client.put('/endpoint', { key: 'value' });
*/
public async put<T>(
endpoint: string,
body: Record<string, any>,
headers: Record<string, string> = {}
): Promise<ApiResponse<T>> {
return this.request<T>(endpoint, {
method: 'PUT',
body: JSON.stringify(body),
headers: {'Content-Type': 'application/json', ...headers},
});
}

/**
* Makes a PATCH request.
* @param {string} endpoint - The API endpoint.
* @param {Record<string, any>} body - The request body.
* @returns {Promise<ApiResponse<T>>} - The API response.
* @template T
* @example
* const response = await client.patch('/endpoint', { key: 'value' });
*/
public async patch<T>(
endpoint: string,
body: Record<string, any>,
headers: Record<string, string> = {}
): Promise<ApiResponse<T>> {
return this.request<T>(endpoint, {
method: 'PATCH',
body: JSON.stringify(body),
headers: {'Content-Type': 'application/json', ...headers},
});
}

/**
* Makes a GET request.
* @param {string} endpoint - The API endpoint.
* @returns {Promise<ApiResponse<T>>} - The API response.
* @template T
* @example
* const response = await client.get('/endpoint');
*/
public async get<T>(
endpoint: string,
headers: Record<string, string> = {}
): Promise<ApiResponse<T>> {
return this.request<T>(endpoint, {
method: 'GET',
headers,
});
}

/**
* Makes a DELETE request.
* @param {string} endpoint - The API endpoint.
* @returns {Promise<ApiResponse<T>>} - The API response.
* @template T
* @example
* const response = await client.delete('/endpoint');
*/
public async delete<T>(
endpoint: string,
headers: Record<string, string> = {}
): Promise<ApiResponse<T>> {
return this.request<T>(endpoint, {
method: 'DELETE',
headers,
});
}

/**
* Get an HTTP client for a specific organization.
* @returns {HttpClient} - An object containing methods for making HTTP requests.
* @example
* const httpClient = client.getHttpClientForOrg();
* const response = await httpClient.get('/endpoint');
*/
public getHttpClientForOrg(): HttpClient {
return {
get: <T>(endpoint: string) => this.get<T>(endpoint),
delete: <T>(endpoint: string) => this.delete<T>(endpoint),
post: <T>(endpoint: string, body: Record<string, any>) => this.post<T>(endpoint, body),
put: <T>(endpoint: string, body: Record<string, any>) => this.put<T>(endpoint, body),
patch: <T>(endpoint: string, body: Record<string, any>) => this.patch<T>(endpoint, body),
};
}

private async getToken(): Promise<string> {
const serviceAppAuthorization = await this.tokenManager.getOrgServiceAppAuthorization(
this.orgId
);
let token = serviceAppAuthorization.serviceAppToken.accessToken;

if (new Date() >= new Date(serviceAppAuthorization.serviceAppToken.expiresAt)) {
await this.tokenManager.refreshServiceAppAccessToken(this.orgId, this.headers);
const refreshedAuthorization = await this.tokenManager.getOrgServiceAppAuthorization(
this.orgId
);
token = refreshedAuthorization.serviceAppToken.accessToken;
}
// TODO: Handle refresh token expiration

return token;
}
}
Loading

0 comments on commit 8f30732

Please sign in to comment.