Skip to content

Commit

Permalink
DEVEXP-380: Support plugins addition in SinchClient parameters (#52)
Browse files Browse the repository at this point in the history
  • Loading branch information
asein-sinch authored Apr 8, 2024
1 parent 95c6ecc commit 65a675d
Show file tree
Hide file tree
Showing 22 changed files with 478 additions and 172 deletions.
17 changes: 2 additions & 15 deletions packages/conversation/src/rest/v1/conversation-domain-api.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import {
Api,
ApiClient,
ApiClientOptions,
ApiFetchClient,
buildOAuth2ApiClientOptions,
ConversationRegion,
Oauth2TokenRequest,
Region,
SinchClientParameters,
UnifiedCredentials,
Expand Down Expand Up @@ -81,25 +80,13 @@ export class ConversationDomainApi implements Api {
if(!Object.values(ConversationRegion).includes((region as unknown) as ConversationRegion)) {
console.warn(`The region '${region}' is not supported for the Conversation API`);
}
const apiClientOptions = this.buildApiClientOptions(this.sinchClientParameters);
const apiClientOptions = buildOAuth2ApiClientOptions(this.sinchClientParameters, 'Conversation');
this.client = new ApiFetchClient(apiClientOptions);
this.client.apiClientOptions.hostname = this.buildHostname(region);
}
return this.client;
}

private buildApiClientOptions(params: SinchClientParameters): ApiClientOptions {
if (!params.projectId || !params.keyId || !params.keySecret) {
throw new Error('Invalid configuration for the Conversation API: '
+ '"projectId", "keyId" and "keySecret" values must be provided');
}
return {
projectId: params.projectId,
requestPlugins: [new Oauth2TokenRequest( params.keyId, params.keySecret)],
useServicePlanId: false,
};
}

private buildHostname(region: Region) {
switch (this.apiName) {
case 'TemplatesV1Api':
Expand Down
2 changes: 1 addition & 1 deletion packages/conversation/src/rest/v1/conversation-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ export class ConversationService {

/**
* Update the default hostname for the Templates API
* @param {string} hostname - The new hostname to use for all the APIs.
* @param {string} hostname - The new hostname to use for the Templates APIs.
*/
public setTemplatesHostname(hostname: string) {
this.templatesV1.setHostname(hostname);
Expand Down
17 changes: 2 additions & 15 deletions packages/fax/src/rest/v3/fax-domain-api.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
import {
Api,
ApiClient,
ApiClientOptions,
ApiFetchClient,
SinchClientParameters,
Oauth2TokenRequest,
UnifiedCredentials,
buildOAuth2ApiClientOptions,
} from '@sinch/sdk-client';

export class FaxDomainApi implements Api {
Expand Down Expand Up @@ -59,23 +58,11 @@ export class FaxDomainApi implements Api {
*/
public getSinchClient(): ApiClient {
if (!this.client) {
const apiClientOptions = this.buildApiClientOptions(this.sinchClientParameters);
const apiClientOptions = buildOAuth2ApiClientOptions(this.sinchClientParameters, 'Fax');
this.client = new ApiFetchClient(apiClientOptions);
this.client.apiClientOptions.hostname = this.sinchClientParameters.faxHostname ?? 'https://fax.api.sinch.com';
}
return this.client;
}

private buildApiClientOptions(params: SinchClientParameters): ApiClientOptions {
if (!params.projectId || !params.keyId || !params.keySecret) {
throw new Error('Invalid configuration for the Fax API: '
+ '"projectId", "keyId" and "keySecret" values must be provided');
}
return {
projectId: params.projectId,
requestPlugins: [new Oauth2TokenRequest( params.keyId, params.keySecret)],
useServicePlanId: false,
};
}

}
18 changes: 4 additions & 14 deletions packages/numbers/src/rest/v1/numbers-domain-api.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import {
Api,
ApiClient, ApiClientOptions,
ApiClient,
ApiFetchClient,
buildOAuth2ApiClientOptions,
SinchClientParameters,
Oauth2TokenRequest, UnifiedCredentials,
UnifiedCredentials,
} from '@sinch/sdk-client';

export class NumbersDomainApi implements Api {
Expand Down Expand Up @@ -57,22 +58,11 @@ export class NumbersDomainApi implements Api {
*/
public getSinchClient(): ApiClient {
if (!this.client) {
const apiClientOptions = this.buildApiClientOptions(this.sinchClientParameters);
const apiClientOptions = buildOAuth2ApiClientOptions(this.sinchClientParameters, 'Numbers');
this.client = new ApiFetchClient(apiClientOptions);
this.client.apiClientOptions.hostname = this.sinchClientParameters.numbersHostname ?? 'https://numbers.api.sinch.com';
}
return this.client;
}

private buildApiClientOptions(params: SinchClientParameters): ApiClientOptions {
if (!params.projectId || !params.keyId || !params.keySecret) {
throw new Error('Invalid configuration for the Numbers API: '
+ '"projectId", "keyId" and "keySecret" values must be provided');
}
return {
projectId: params.projectId,
requestPlugins: [new Oauth2TokenRequest( params.keyId, params.keySecret)],
useServicePlanId: false,
};
}
}
124 changes: 87 additions & 37 deletions packages/sdk-client/src/api/api-client-options-helper.ts
Original file line number Diff line number Diff line change
@@ -1,37 +1,87 @@
// import { ApiTokenRequest, Oauth2TokenRequest, SigningRequest, XTimestampRequest } from '../plugins';
// // import { ApiClientOptions } from './api-client-options';
//
// export const buildApiClientOptionsForProjectId = (
// projectId: string,
// keyId: string,
// keySecret: string,
// ) => {
// return {
// projectId,
// requestPlugins: [new Oauth2TokenRequest(keyId, keySecret)],
// useServicePlanId: false,
// };
// };
//
// export const buildApiClientOptionForServicePlanId = (
// servicePlanId: string,
// apiToken: string,
// ) => {
// return {
// projectId: servicePlanId,
// requestPlugins: [new ApiTokenRequest(apiToken)],
// useServicePlanId: true,
// };
// };
//
// export const buildApiClientOptionForApplication = (
// applicationKey: string,
// applicationSecret: string,
// ) => {
// return {
// requestPlugins: [
// new XTimestampRequest(),
// new SigningRequest(applicationKey, applicationSecret),
// ],
// };
// };
import { Region, SinchClientParameters } from '../domain';
import { ApiClientOptions } from './api-client-options';
import {
ApiTokenRequest,
Oauth2TokenRequest,
SigningRequest,
XTimestampRequest,
} from '../plugins';

export const buildOAuth2ApiClientOptions = (params: SinchClientParameters, apiName: string): ApiClientOptions => {
if (!params.projectId || !params.keyId || !params.keySecret) {
throw new Error(`Invalid configuration for the ${apiName} API: "projectId", "keyId" and "keySecret" values must be provided`);
}
const apiClientOptions: ApiClientOptions = {
projectId: params.projectId,
requestPlugins: [new Oauth2TokenRequest(params.keyId, params.keySecret, params.authHostname)],
useServicePlanId: false,
};
addPlugins(apiClientOptions, params);
return apiClientOptions;
};

export const buildApplicationSignedApiClientOptions = (
params: SinchClientParameters, apiName: string,
): ApiClientOptions => {
if (!params.applicationKey || !params.applicationSecret) {
throw new Error(`Invalid configuration for the ${apiName} API: "applicationKey" and "applicationSecret" values must be provided`);
}
const apiClientOptions: ApiClientOptions = {
requestPlugins: [
new XTimestampRequest(),
new SigningRequest(params.applicationKey, params.applicationSecret),
],
};
addPlugins(apiClientOptions, params);
return apiClientOptions;
};

export const buildFlexibleOAuth2OrApiTokenApiClientOptions = (
params: SinchClientParameters, region: Region, apiName: string,
): ApiClientOptions => {
let apiClientOptions: ApiClientOptions | undefined;
// Check the region: if US or EU, try to use the OAuth2 authentication with the access key / secret under the project Id
if (!params.forceServicePlanIdUsageForSmsApi
&& (region === Region.UNITED_STATES || region === Region.EUROPE)) {
// Let's check the required parameters for OAuth2 authentication
if (params.projectId && params.keyId && params.keySecret) {
apiClientOptions = {
projectId: params.projectId,
requestPlugins: [new Oauth2TokenRequest(params.keyId, params.keySecret, params.authHostname)],
useServicePlanId: false,
};
}
}
if (!apiClientOptions) {
// The API client options couldn't be initialized for with the projectId unified authentication.
// Let's try with the servicePlanId
if (params.servicePlanId && params.apiToken) {
apiClientOptions = {
projectId: params.servicePlanId,
requestPlugins: [new ApiTokenRequest(params.apiToken)],
useServicePlanId: true,
};
}
}
if (!apiClientOptions) {
throw new Error(`Invalid parameters for the ${apiName} API: check your configuration`);
}
addPlugins(apiClientOptions, params);
return apiClientOptions;
};

const addPlugins = (apiClientOptions: ApiClientOptions, params: SinchClientParameters) => {
if (params.requestPlugins && params.requestPlugins.length > 0) {
if (!apiClientOptions.requestPlugins) {
apiClientOptions.requestPlugins = [];
}
apiClientOptions.requestPlugins.push(...params.requestPlugins);
}
if (params.responsePlugins && params.responsePlugins.length > 0) {
if (!apiClientOptions.responsePlugins) {
apiClientOptions.responsePlugins = [];
}
apiClientOptions.responsePlugins.push(...params.responsePlugins);
}
return apiClientOptions;
};
8 changes: 7 additions & 1 deletion packages/sdk-client/src/api/api-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import { RequestBody, RequestOptions } from '../plugins/core/request-plugin';
import { ApiClientOptions } from './api-client-options';
import { Headers } from 'node-fetch';
import FormData = require('form-data');
import { FileBuffer } from './api-interface';

export enum PaginationEnum {
NONE,
Expand Down Expand Up @@ -51,6 +50,13 @@ export interface PaginatedApiProperties {
dataKey: string,
}

export interface FileBuffer {
/** Name of the file extracted from the 'content-disposition' header */
fileName: string;
/** File content as Buffer */
buffer: Buffer;
}

/**
* API Client used to call the server
*/
Expand Down
7 changes: 0 additions & 7 deletions packages/sdk-client/src/api/api-interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,3 @@ export interface Api {
/** API Client used to process the calls to the API */
client?: ApiClient;
}

export interface FileBuffer {
/** Name of the file extracted from the 'content-disposition' header */
fileName: string;
/** File content as Buffer */
buffer: Buffer;
}
34 changes: 28 additions & 6 deletions packages/sdk-client/src/api/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,28 @@
export * from './api-client-options';
// export * from './api-client-options-helper';
export * from './api-client';
export * from './api-errors';
export * from './api-interface';
export * from './callback-webhooks-interface';
export {
ApiClientOptions,
} from './api-client-options';
export {
buildOAuth2ApiClientOptions,
buildApplicationSignedApiClientOptions,
buildFlexibleOAuth2OrApiTokenApiClientOptions,
} from './api-client-options-helper';
export {
ApiClient,
ApiListPromise,
FileBuffer,
PageResult,
PaginatedApiProperties,
PaginationEnum,
} from './api-client';
export {
GenericError,
RequestFailedError,
EmptyResponseError,
ResponseJSONParseError,
} from './api-errors';
export {
Api,
} from './api-interface';
export {
CallbackProcessor,
} from './callback-webhooks-interface';
3 changes: 2 additions & 1 deletion packages/sdk-client/src/client/api-client-helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ import {
RequestPlugin,
RequestPluginEnum,
} from '../plugins/core/request-plugin';
import { ApiCallParameters, ApiCallParametersWithPagination, ErrorContext, GenericError } from '../api';
import { ApiCallParameters, ApiCallParametersWithPagination } from '../api/api-client';
import { ErrorContext, GenericError } from '../api/api-errors';

export const manageExpiredToken = async (
apiCallParameters: ApiCallParameters | ApiCallParametersWithPagination,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {
PageResult,
PaginatedApiProperties,
PaginationEnum,
} from '../api';
} from '../api/api-client';
import { RequestOptions } from '../plugins/core/request-plugin';

class SinchIterator<T> implements AsyncIterator<T> {
Expand Down
14 changes: 9 additions & 5 deletions packages/sdk-client/src/client/api-fetch-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,20 @@ import { ExceptionResponse } from '../plugins/exception';
import { TimezoneResponse } from '../plugins/timezone';
import {
ApiClient,
ApiCallParameters,
ApiCallParametersWithPagination,
PageResult,
FileBuffer,
} from '../api/api-client';
import {
ApiClientOptions,
} from '../api/api-client-options';
import {
EmptyResponseError,
ErrorContext,
GenericError,
ApiCallParameters,
ResponseJSONParseError,
ApiCallParametersWithPagination,
PageResult,
FileBuffer,
} from '../api';
} from '../api/api-errors';
import fetch, { Response, Headers } from 'node-fetch';
import FormData = require('form-data');
import { buildErrorContext, manageExpiredToken, reviveDates } from './api-client-helpers';
Expand Down
11 changes: 10 additions & 1 deletion packages/sdk-client/src/domain/domain-interface.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
import { RequestPlugin } from '../plugins/core/request-plugin';
import { ResponsePlugin } from '../plugins/core/response-plugin';

export interface SinchClientParameters extends
Partial<UnifiedCredentials>,
Partial<ServicePlanIdCredentials>,
Partial<ApplicationCredentials>,
ApiHostname {}
ApiHostname,
ApiPlugins {}

export interface UnifiedCredentials {
/** The project ID associated with the API Client. You can find this on your [Dashboard](https://dashboard.sinch.com/account/access-keys). */
Expand Down Expand Up @@ -47,6 +51,11 @@ export interface ApiHostname {
voiceApplicationManagementHostname?: string;
}

export interface ApiPlugins {
requestPlugins?: RequestPlugin[];
responsePlugins?: ResponsePlugin<any>[];
}

export const isUnifiedCredentials = (credentials: any): credentials is UnifiedCredentials => {
const candidate = (credentials) as UnifiedCredentials;
return candidate.projectId !== undefined
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { EmptyResponseError, RequestFailedError } from '../../api';
import { EmptyResponseError, RequestFailedError } from '../../api/api-errors';
import { PluginRunner } from '../core';
import { ResponsePlugin, ResponsePluginContext } from '../core/response-plugin';

Expand Down
3 changes: 2 additions & 1 deletion packages/sdk-client/src/plugins/oauth2/oauth2-api.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Api, ApiClient } from '../../api';
import { Api } from '../../api/api-interface';
import { ApiClient } from '../../api/api-client';

export interface Response {
access_token: string;
Expand Down
Loading

0 comments on commit 65a675d

Please sign in to comment.