Skip to content

Commit ea802fc

Browse files
authored
Make the editor more resilient to crashes when network proxies are modifying API responses (#8061)
1 parent 6f5431c commit ea802fc

File tree

20 files changed

+907
-271
lines changed

20 files changed

+907
-271
lines changed
Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
// @flow
2+
3+
export const ensureIsObject = ({
4+
data,
5+
endpointName,
6+
}: {|
7+
data: any,
8+
endpointName: string,
9+
|}): any => {
10+
if (!data || typeof data !== 'object') {
11+
throw new Error(
12+
`Invalid response from endpoint ${endpointName}, was expecting an object.`
13+
);
14+
}
15+
return data;
16+
};
17+
18+
export const ensureIsObjectOrNull = ({
19+
data,
20+
endpointName,
21+
}: {|
22+
data: any,
23+
endpointName: string,
24+
|}): any => {
25+
if (data !== null && (!data || typeof data !== 'object')) {
26+
throw new Error(
27+
`Invalid response from endpoint ${endpointName}, was expecting an object or null.`
28+
);
29+
}
30+
return data;
31+
};
32+
33+
export const ensureObjectHasProperty = ({
34+
data,
35+
propertyName,
36+
endpointName,
37+
}: {|
38+
data: any,
39+
propertyName: string,
40+
endpointName: string,
41+
|}): any => {
42+
if (!data || typeof data !== 'object') {
43+
throw new Error(
44+
`Invalid response from endpoint ${endpointName}, was expecting an object.`
45+
);
46+
}
47+
if (data[propertyName] === undefined) {
48+
throw new Error(
49+
`Invalid response from endpoint ${endpointName}, was expecting an object with a ${propertyName} field.`
50+
);
51+
}
52+
return data;
53+
};
54+
55+
export const ensureIsNullOrObjectHasProperty = ({
56+
data,
57+
propertyName,
58+
endpointName,
59+
}: {|
60+
data: any,
61+
propertyName: string,
62+
endpointName: string,
63+
|}): any => {
64+
if (data !== null) {
65+
if (!data || typeof data !== 'object') {
66+
throw new Error(
67+
`Invalid response from endpoint ${endpointName}, was expecting an object or null.`
68+
);
69+
}
70+
if (data[propertyName] === undefined) {
71+
throw new Error(
72+
`Invalid response from endpoint ${endpointName}, was expecting an object with a ${propertyName} field.`
73+
);
74+
}
75+
}
76+
return data;
77+
};
78+
79+
export const ensureIsArray = ({
80+
data,
81+
endpointName,
82+
}: {|
83+
data: any,
84+
endpointName: string,
85+
|}): Array<any> => {
86+
if (!Array.isArray(data)) {
87+
throw new Error(
88+
`Invalid response from endpoint ${endpointName}, was expecting an array.`
89+
);
90+
}
91+
return data;
92+
};
93+
94+
export const ensureIsArrayOrNull = ({
95+
data,
96+
endpointName,
97+
}: {|
98+
data: any,
99+
endpointName: string,
100+
|}): Array<any> | null => {
101+
if (data !== null && !Array.isArray(data)) {
102+
throw new Error(
103+
`Invalid response from endpoint ${endpointName}, was expecting an array or null.`
104+
);
105+
}
106+
return data;
107+
};
108+
109+
export const ensureIsString = ({
110+
data,
111+
endpointName,
112+
}: {|
113+
data: any,
114+
endpointName: string,
115+
|}): string => {
116+
if (typeof data !== 'string') {
117+
throw new Error(
118+
`Invalid response from endpoint ${endpointName}, was expecting a string.`
119+
);
120+
}
121+
return data;
122+
};
123+
124+
export const ensureIsObjectWithPropertyOfType = ({
125+
data,
126+
propertyName,
127+
propertyType,
128+
endpointName,
129+
}: {|
130+
data: any,
131+
propertyName: string,
132+
propertyType: 'string' | 'number' | 'boolean' | 'object' | 'array',
133+
endpointName: string,
134+
|}): any => {
135+
if (!data || typeof data !== 'object') {
136+
throw new Error(
137+
`Invalid response from endpoint ${endpointName}, was expecting an object.`
138+
);
139+
}
140+
if (data[propertyName] === undefined) {
141+
throw new Error(
142+
`Invalid response from endpoint ${endpointName}, was expecting an object with a ${propertyName} field.`
143+
);
144+
}
145+
const propertyValue = data[propertyName];
146+
let isValidType = false;
147+
if (propertyType === 'array') {
148+
isValidType = Array.isArray(propertyValue);
149+
} else {
150+
isValidType = typeof propertyValue === propertyType;
151+
}
152+
if (!isValidType) {
153+
throw new Error(
154+
`Invalid response from endpoint ${endpointName}, was expecting an object with a ${propertyName} field of type ${propertyType}.`
155+
);
156+
}
157+
return data;
158+
};

newIDE/app/src/Utils/GDevelopServices/Analytics.js

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
// @flow
22
import axios from 'axios';
33
import { GDevelopAnalyticsApi } from './ApiConfigs';
4+
import {
5+
ensureIsNullOrObjectHasProperty,
6+
ensureIsArrayOrNull,
7+
} from '../DataValidator';
48

59
export type GameMetrics = {
610
date: string,
@@ -70,7 +74,11 @@ export const getGameMetrics = async (
7074
});
7175

7276
if (response.status === 404) return null;
73-
return response.data;
77+
return ensureIsNullOrObjectHasProperty({
78+
data: response.data,
79+
propertyName: 'date',
80+
endpointName: '/game-metrics of Analytics API',
81+
});
7482
};
7583

7684
export const getGameMetricsFrom = async (
@@ -94,5 +102,8 @@ export const getGameMetricsFrom = async (
94102
});
95103

96104
if (response.status === 404) return null;
97-
return response.data;
105+
return ensureIsArrayOrNull({
106+
data: response.data,
107+
endpointName: '/game-metrics of Analytics API',
108+
});
98109
};

newIDE/app/src/Utils/GDevelopServices/Announcement.js

Lines changed: 9 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import axios from 'axios';
33
import { GDevelopReleaseApi } from './ApiConfigs';
44
import { type MessageByLocale } from '../i18n/MessageByLocale';
5+
import { ensureIsArray } from '../DataValidator';
56

67
export type Announcement = {
78
id: string,
@@ -34,20 +35,16 @@ export const listAllAnnouncements = async (): Promise<Array<Announcement>> => {
3435
const response = await axios.get(
3536
`${GDevelopReleaseApi.baseUrl}/announcement`
3637
);
37-
const announcements = response.data;
38-
if (!Array.isArray(announcements)) {
39-
throw new Error('Invalid response from the announcements API');
40-
}
41-
42-
return announcements;
38+
return ensureIsArray({
39+
data: response.data,
40+
endpointName: '/announcement of Release API',
41+
});
4342
};
4443

4544
export const listAllPromotions = async (): Promise<Array<Promotion>> => {
4645
const response = await axios.get(`${GDevelopReleaseApi.baseUrl}/promotion`);
47-
const promotions = response.data;
48-
if (!Array.isArray(promotions)) {
49-
throw new Error('Invalid response from the promotions API');
50-
}
51-
52-
return promotions;
46+
return ensureIsArray({
47+
data: response.data,
48+
endpointName: '/promotion of Release API',
49+
});
5350
};

0 commit comments

Comments
 (0)