Skip to content

Commit 491f008

Browse files
committed
feat:error handling and region support
Signed-off-by: Amitkanswal <[email protected]>
1 parent 1922233 commit 491f008

File tree

5 files changed

+113
-42
lines changed

5 files changed

+113
-42
lines changed

src/types.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { GenericObjectType } from "./types/common.types";
77
import { Entry } from "./types/entry.types";
88
import { Asset, ContentType, Schema, StackDetail } from "./types/stack.types";
99
import { OrganizationDetails } from "./types/organization.types";
10+
import { ServiceURLsMap } from './types/api.type';
1011
import { User } from "./types/user.types";
1112
import Window from "./window";
1213

@@ -102,6 +103,7 @@ declare interface ICommonInitData {
102103
type: LocationType;
103104
user: User;
104105
manifest?: Manifest;
106+
serviceDomainUrls?: ServiceURLsMap;
105107
}
106108

107109
export declare interface IOrgFullPageLocationInitData extends ICommonInitData {
@@ -254,4 +256,5 @@ export enum Region {
254256
AZURE_NA = "AZURE_NA",
255257
AZURE_EU = "AZURE_EU",
256258
GCP_NA = "GCP_NA",
259+
GCP_EU = "GCP_EU",
257260
}

src/types/api.type.ts

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,19 @@
1-
import { AxiosRequestConfig, AxiosResponse, } from 'axios'
2-
export type RequestConfig = AxiosRequestConfig
3-
export type ProxyResponse = AxiosResponse
1+
import { AxiosRequestConfig, AxiosResponse } from "axios";
2+
export type RequestConfig = AxiosRequestConfig;
3+
export type ProxyResponse = AxiosResponse;
4+
5+
export type ServiceURLsMap = {
6+
CMA: string;
7+
};
8+
9+
enum SupportedServices {
10+
CMA = "CMA",
11+
}
12+
13+
export type RequestInitConfig = RequestInit & {
14+
service?: keyof typeof SupportedServices;
15+
};
16+
17+
export type RequestConfigWithBaseUrl = RequestInitConfig & {
18+
baseURL: string;
19+
};

src/uiLocation.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { AxiosRequestConfig, AxiosResponse } from 'axios';
12
import postRobot from "post-robot";
23
import EventEmitter from "wolfy87-eventemitter";
34

@@ -34,7 +35,7 @@ import { User } from "./types/user.types";
3435
import { formatAppRegion, onData, onError } from "./utils/utils";
3536
import Window from "./window";
3637
import { dispatchApiRequest, dispatchAdapter } from './utils/adapter';
37-
import { AxiosRequestConfig, AxiosResponse } from 'axios';
38+
import { RequestInitConfig, ServiceURLsMap } from "./types/api.type";
3839

3940
const emitter = new EventEmitter();
4041

@@ -69,6 +70,9 @@ class UiLocation {
6970
*/
7071
private config: GenericObjectType;
7172

73+
74+
readonly hostedEndpoints: ServiceURLsMap
75+
7276
/**
7377
* This holds the instance of Cross-domain communication library for posting messages between windows.
7478
*/
@@ -154,7 +158,7 @@ class UiLocation {
154158
});
155159

156160
this.metadata = new Metadata(postRobot);
157-
161+
158162
this.config = initializationData.config ?? {};
159163

160164
this.ids = {
@@ -186,6 +190,8 @@ class UiLocation {
186190

187191
this.region = formatAppRegion(initializationData.region);
188192

193+
this.hostedEndpoints = initializationData.serviceDomainUrls ?? { CMA: '' };
194+
189195
const stack = new Stack(initializationData.stack, postRobot, {
190196
currentBranch: initializationData.currentBranch,
191197
});
@@ -476,7 +482,7 @@ class UiLocation {
476482
* Method used to make an API request to the Contentstack's CMA APIs.
477483
*/
478484

479-
api = (url: string, option?: RequestInit): Promise<Response> => dispatchApiRequest(url, option) as Promise<Response>;
485+
api = (url: string, option?: RequestInitConfig): Promise<Response> => dispatchApiRequest(url,this.hostedEndpoints, option) as Promise<Response>;
480486

481487
/**
482488
* Method used to create an adapter for management sdk.

src/utils/adapter.ts

Lines changed: 51 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,47 +1,69 @@
1-
import PostRobot from 'post-robot';
2-
import { AxiosRequestConfig, AxiosResponse } from 'axios';
3-
import { onError, fetchToAxiosConfig } from './utils';
1+
import PostRobot from "post-robot";
2+
import { AxiosRequestConfig, AxiosResponse, isAxiosError } from "axios";
3+
4+
import { onError, fetchToAxiosConfig, serializeAxiosResponse } from "./utils";
5+
import { RequestInitConfig, ServiceURLsMap } from "../types/api.type";
6+
7+
export const resolveBaseUrl = (hostingRegion:ServiceURLsMap, option?:RequestInitConfig)=>{
8+
return option?.service? hostingRegion[option.service]: hostingRegion.CMA
9+
}
10+
411

512
/**
613
* Dispatches a request using PostRobot.
714
* @param postRobot - The PostRobot instance.
815
* @returns A function that takes AxiosRequestConfig and returns a promise.
916
*/
10-
export const dispatchAdapter = (postRobot: typeof PostRobot) => (config: AxiosRequestConfig)=> {
17+
export const dispatchAdapter = (postRobot: typeof PostRobot) => (config: AxiosRequestConfig): Promise<AxiosResponse> => {
1118
return postRobot
12-
.sendToParent("apiAdapter", config )
13-
.then(({ data }) => ({ ...data, config }))
19+
.sendToParent("apiAdapter", config)
20+
.then((event:unknown) => {
21+
const { data } = event as { data: AxiosResponse };
22+
if (data.status >= 400) {
23+
throw serializeAxiosResponse(data, config);
24+
}
25+
return serializeAxiosResponse(data, config);
26+
})
1427
.catch(onError);
1528
};
16-
1729
/**
1830
* Dispatches an API request using axios and PostRobot.
1931
* @param url - The URL of the API endpoint.
2032
* @param options - Optional request options.
2133
* @returns A promise that resolves to a partial Response object.
2234
*/
23-
export const dispatchApiRequest = async (url: string, options?: RequestInit): Promise<Response> => {
24-
try {
25-
const config = fetchToAxiosConfig(url, options);
26-
const responseData = await dispatchAdapter(PostRobot)(config) as AxiosResponse;
27-
return new Response(responseData.data,{
28-
status: responseData.status,
29-
statusText: responseData.statusText,
30-
headers: new Headers(responseData.config.headers || {}),
31-
});
35+
export const dispatchApiRequest = async (
36+
url: string,
37+
hostedUrl:ServiceURLsMap,
38+
options?: RequestInitConfig,
39+
): Promise<Response> => {
40+
try {
41+
const updatedOptions = {...options, baseURL: resolveBaseUrl(hostedUrl, options)};
42+
const config = fetchToAxiosConfig(url, updatedOptions);
43+
const responseData = (await dispatchAdapter(PostRobot)(
44+
config
45+
)) as AxiosResponse;
46+
47+
if (isAxiosError(responseData)) {
48+
throw responseData;
49+
}
50+
const response = new Response(responseData?.data, {
51+
status: responseData.status,
52+
statusText: responseData.statusText,
53+
headers: new Headers(responseData.config.headers || {}),
54+
});
55+
return response
56+
57+
} catch (error: any) {
58+
const data = error.response?.data || error.data;
59+
const status = error.response?.status || error.status || 500;
60+
const statusText = error.response?.statusText || error.statusText || "Internal Server Error";
61+
const headers = new Headers(error.response?.headers || error.headers);
3262

33-
} catch (error: any) {
34-
if (error.response) {
35-
const fetchResponse = new Response(error.response.data, {
36-
status: error.response.status,
37-
statusText: error.response.statusText,
38-
headers: new Headers(error.response.headers)
63+
throw new Response(data, {
64+
status,
65+
statusText,
66+
headers,
3967
});
40-
return Promise.reject(fetchResponse);
41-
} else if (error.request) {
42-
return Promise.reject(new Response(null, { status: 0, statusText: 'Network Error' }));
43-
} else {
44-
return Promise.reject(new Response(null, { status: 0, statusText: error.message }));
45-
}
4668
}
47-
};
69+
};

src/utils/utils.ts

Lines changed: 31 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { Region } from "../types";
2-
import { AxiosHeaders, AxiosRequestConfig } from "axios";
2+
import { AxiosHeaders, AxiosRequestConfig, AxiosResponse } from "axios";
3+
import { RequestConfigWithBaseUrl } from '../types/api.type';
34

45
export function onData<Data extends Record<string, any>>(data: { data: Data }) {
56
if (typeof data.data === "string") {
@@ -50,6 +51,14 @@ export function getPreferredBodyElement(nodeCollection: HTMLCollection) {
5051
return rootElement || nodeCollection[0];
5152
}
5253

54+
function isAbsoluteURL(url) {
55+
try {
56+
new URL(url);
57+
return true;
58+
} catch (e) {
59+
return false;
60+
}
61+
}
5362

5463
export const convertHeaders = (headers: HeadersInit): AxiosHeaders => {
5564
const axiosHeaders = new AxiosHeaders();
@@ -69,17 +78,32 @@ export const convertHeaders = (headers: HeadersInit): AxiosHeaders => {
6978
return axiosHeaders;
7079
};
7180

72-
export const fetchToAxiosConfig = (url: string, options: RequestInit = {}): AxiosRequestConfig => {
81+
export const fetchToAxiosConfig = (url: string ,options?: RequestConfigWithBaseUrl): AxiosRequestConfig => {
82+
7383
const axiosConfig: AxiosRequestConfig = {
7484
url,
75-
method: options.method || 'GET',
76-
headers: options.headers ? convertHeaders({...options.headers}) : {},
77-
data: options.body,
85+
method: options?.method || 'GET',
86+
headers: options?.headers ? convertHeaders({...options?.headers}) : {},
87+
data: options?.body,
7888
};
79-
80-
if (options.credentials === 'include') {
89+
90+
if (!isAbsoluteURL(url)) {
91+
axiosConfig.baseURL = options?.baseURL;
92+
}
93+
94+
if (options?.credentials === 'include') {
8195
axiosConfig.withCredentials = true;
8296
}
8397

8498
return axiosConfig;
99+
}
100+
101+
export const serializeAxiosResponse = (responseData: AxiosResponse, config) => {
102+
return {
103+
data: responseData.data,
104+
status: responseData.status,
105+
statusText: responseData.statusText,
106+
headers: responseData.headers as AxiosHeaders,
107+
config,
108+
}
85109
}

0 commit comments

Comments
 (0)