Skip to content

Commit 68da1aa

Browse files
committed
better error handling for invalid url, show error message on error instead of relying defaulting to query result, better handling for edge cases for invalid user input variable
1 parent 93c1d41 commit 68da1aa

File tree

6 files changed

+156
-97
lines changed

6 files changed

+156
-97
lines changed

main_process/main_trpcController.js

Lines changed: 89 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -105,64 +105,75 @@ const trpcController = {
105105
throw 'is String';
106106
}
107107
} catch (error) {
108-
return JSON.parse(str);
108+
if (error === 'is String') {
109+
return JSON.parse(str);
110+
} else {
111+
return { error };
112+
}
109113
}
110114
}
111-
const { headers, cookie } = reqResObject.request; //grab the headers and cookie inputted by user
115+
try {
116+
const { headers, cookie } = reqResObject.request; //grab the headers and cookie inputted by user
112117

113-
const formattedHeaders = {}; //header object
114-
headers.forEach((head) => {
115-
if (head.active) {
116-
formattedHeaders[head.key] = head.value;
117-
}
118-
});
119-
if (cookie) {
120-
//parses cookie data to attach to option object
121-
cookie.forEach((cookie) => {
122-
const cookieString = `${cookie.key}=${cookie.value}`;
123-
// attach to formattedHeaders so options object includes this
124-
formattedHeaders.cookie = formattedHeaders.cookie
125-
? formattedHeaders.cookie + ';' + cookieString
126-
: cookieString;
118+
const formattedHeaders = {}; //header object
119+
headers.forEach((head) => {
120+
if (head.active) {
121+
formattedHeaders[head.key] = head.value;
122+
}
127123
});
128-
}
124+
if (cookie) {
125+
//parses cookie data to attach to option object
126+
cookie.forEach((cookie) => {
127+
const cookieString = `${cookie.key}=${cookie.value}`;
128+
// attach to formattedHeaders so options object includes this
129+
formattedHeaders.cookie = formattedHeaders.cookie
130+
? formattedHeaders.cookie + ';' + cookieString
131+
: cookieString;
132+
});
133+
}
129134

130-
// here we will construct the url and the body using data inside the reqRes obj
131-
// because a user could batch procedures together/ we need to account for the request object to contain data for both a get request and a post request
132-
let url = '';
133-
const body = {};
134-
procedures.forEach((procedure, index) => {
135-
if (procedure.variable) {
136-
body[index] = parseString(procedure.variable);
135+
const outputObj = {
136+
// the main object that will get returned
137+
method,
138+
mode: 'cors', // no-cors, cors, *same-origin
139+
cache: 'no-cache', // *default, no-cache, reload, force-cache, only-if-cached
140+
credentials: 'include', // include, *same-origin, omit
141+
headers: formattedHeaders,
142+
redirect: 'follow', // manual, *follow, error
143+
referrer: 'no-referrer', // no-referrer, *client
144+
};
145+
// here we will construct the url and the body using data inside the reqRes obj
146+
// because a user could batch procedures together/ we need to account for the request object to contain data for both a get request and a post request
147+
let url = '';
148+
const body = {};
149+
procedures.forEach((procedure, index) => {
150+
if (procedure.variable) {
151+
body[index] = parseString(procedure.variable);
152+
if (body[index].error) {
153+
throw "Invalid variable input: Please check all procedure's input to be in json string format";
154+
}
155+
} else {
156+
body[index] = {};
157+
}
158+
url = url ? url + ',' + procedure.endpoint : procedure.endpoint;
159+
});
160+
if (method === 'POST') {
161+
url = reqResObject.url + '/' + url + '?batch=1';
162+
outputObj.body = body;
137163
} else {
138-
body[index] = {};
164+
url =
165+
reqResObject.url +
166+
'/' +
167+
url +
168+
'?batch=1' +
169+
`&input=${encodeURIComponent(JSON.stringify(body))}`;
139170
}
140-
url = url ? url + ',' + procedure.endpoint : procedure.endpoint;
141-
});
142-
if (method === 'POST') {
143-
url = reqResObject.url + '/' + url + '?batch=1';
144-
outputObj.body = body;
145-
} else {
146-
url =
147-
reqResObject.url +
148-
'/' +
149-
url +
150-
'?batch=1' +
151-
`&input=${encodeURIComponent(JSON.stringify(body))}`;
152-
}
171+
outputObj.url = url;
153172

154-
const outputObj = {
155-
// the main object that will get returned
156-
url,
157-
method,
158-
mode: 'cors', // no-cors, cors, *same-origin
159-
cache: 'no-cache', // *default, no-cache, reload, force-cache, only-if-cached
160-
credentials: 'include', // include, *same-origin, omit
161-
headers: formattedHeaders,
162-
redirect: 'follow', // manual, *follow, error
163-
referrer: 'no-referrer', // no-referrer, *client
164-
};
165-
return outputObj;
173+
return outputObj;
174+
} catch (error) {
175+
return { error };
176+
}
166177
},
167178

168179
openRequest: async function (event, reqRes) {
@@ -171,23 +182,35 @@ const trpcController = {
171182
//filter the procedures into either query or mutate in order to make the appropriate http request for each procedure
172183
// all query procedure will be made with a get request
173184
// all mutation procedure will be made with a post request
174-
const getReq = procedures.filter(
175-
(procedure) => procedure.method === 'QUERY'
176-
);
177-
const postReq = procedures.filter(
178-
(procedure) => procedure.method === 'MUTATE'
179-
);
180-
181-
// parsing data from the reqRes object to construct either a get/post option object that contains everything we need to make our get/post http request
182-
const getOption = getReq.length
183-
? this.parseOptionForFetch(reqRes, 'GET', getReq)
184-
: false;
185-
const postOption = postReq.length
186-
? this.parseOptionForFetch(reqRes, 'POST', postReq)
187-
: false;
188-
189-
this.makeFetch(event, reqRes, getOption, postOption); // where we will finally make the request inside of makeFetch
185+
try {
186+
const getReq = procedures.filter(
187+
(procedure) => procedure.method === 'QUERY'
188+
);
189+
const postReq = procedures.filter(
190+
(procedure) => procedure.method === 'MUTATE'
191+
);
192+
193+
// parsing data from the reqRes object to construct either a get/post option object that contains everything we need to make our get/post http request
194+
const getOption = getReq.length
195+
? this.parseOptionForFetch(reqRes, 'GET', getReq)
196+
: false;
197+
const postOption = postReq.length
198+
? this.parseOptionForFetch(reqRes, 'POST', postReq)
199+
: false;
200+
if (getOption.error || postOption.error) {
201+
throw getOption.error ? getOption.error : postOption.error;
202+
} else {
203+
this.makeFetch(event, reqRes, getOption, postOption); // where we will finally make the request inside of makeFetch
204+
}
205+
} catch (error) {
206+
//if error we will push the error into event to be display
207+
reqRes.connection = 'error';
208+
reqRes.error = error;
209+
reqRes.response.events.push(error);
210+
event.sender.send('reqResUpdate', reqRes); // send updated object back to front end
211+
}
190212
},
213+
191214
cookieFormatter(cookieArray) {
192215
return cookieArray.map((eachCookie) => {
193216
const cookieFormat = {

src/client/components/main/GraphQL-composer/GraphQLComposer.tsx

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import { v4 as uuid } from 'uuid';
55
import historyController from '../../../controllers/historyController';
66
// Import local components
77

8-
98
import HeaderEntryForm from '../sharedComponents/requestForms/HeaderEntryForm';
109
import GraphQLMethodAndEndpointEntryForm from './GraphQLMethodAndEndpointEntryForm';
1110
import CookieEntryForm from '../sharedComponents/requestForms/CookieEntryForm';
@@ -122,7 +121,7 @@ export default function GraphQLComposer(props: $TSFixMe) {
122121
.splice(1)
123122
.join('/')
124123
.replace(/\/{2,}/g, '/')}`;
125-
124+
126125
if (path.charAt(path.length - 1) === '/' && path.length > 1) {
127126
path = path.substring(0, path.length - 1);
128127
}

src/client/components/main/TRPC-composer/TRPCComposer.tsx

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,24 @@ export default function TRPCComposer(props) {
8989
setShowSubscription(bool);
9090
};
9191

92+
const requestValidationCheck = () => {
93+
interface ValidationMessage {
94+
uri?: string;
95+
json?: string;
96+
}
97+
const validationMessage: ValidationMessage = {};
98+
// Error conditions...
99+
if (/https?:\/\/$|wss?:\/\/$/.test(url)) {
100+
//if url is only http/https/ws/wss://
101+
validationMessage.uri = 'Enter a valid URI';
102+
}
103+
if (!/(https?:\/\/)|(wss?:\/\/)/.test(url)) {
104+
//if url doesn't have http/https/ws/wss://
105+
validationMessage.uri = 'Enter a valid URI';
106+
}
107+
return validationMessage;
108+
};
109+
92110
const [procedures, proceduresDipatch] = useReducer(reducer, [
93111
//userReducer hook to manage the main state (the array of procedures)
94112
PROCEDURE_DEFAULT,
@@ -120,6 +138,7 @@ export default function TRPCComposer(props) {
120138
newRequestStreams,
121139
currentTab,
122140
setWarningMessage,
141+
warningMessage,
123142
reqResItemAdded,
124143
} = props;
125144

@@ -137,6 +156,11 @@ export default function TRPCComposer(props) {
137156
};
138157

139158
const sendRequest = async () => {
159+
const warnings = requestValidationCheck();
160+
if (Object.keys(warnings).length > 0) {
161+
setWarningMessage(warnings);
162+
return;
163+
}
140164
// THE MAIN FUNCTION to both compose the main reqRes object as well as sending it to the back end
141165
const id = uuid();
142166
const headers = newRequest.newRequestHeaders.headersArr.filter(
@@ -200,7 +224,8 @@ export default function TRPCComposer(props) {
200224
style={{ overflowX: 'hidden' }}
201225
>
202226
<TRPCMethodAndEndpointEntryForm
203-
subscriptionHandler={subscriptionHandler}
227+
setWarningMessage={setWarningMessage}
228+
warningMessage={warningMessage}
204229
/>
205230

206231
<HeaderEntryForm

src/client/components/main/TRPC-composer/TRPCMethodAndEndpointEntryForm.tsx

Lines changed: 26 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,16 @@ import { useSelector, useDispatch } from 'react-redux';
77
import { RootState } from '../../../toolkit-refactor/store';
88
import { fieldsReplaced } from '../../../toolkit-refactor/slices/newRequestFieldsSlice';
99

10-
const TRPCMethodAndEndpointEntryForm = () => {
10+
const TRPCMethodAndEndpointEntryForm = (props) => {
1111
const requestFields = useSelector(
1212
(state: RootState) => state.newRequestFields
1313
);
1414
const dispatch = useDispatch();
15-
15+
const clearWarningIfApplicable = () => {
16+
if (props.warningMessage.uri) props.setWarningMessage({});
17+
};
1618
const urlChangeHandler = (e: React.ChangeEvent<HTMLInputElement>) => {
19+
clearWarningIfApplicable();
1720
//update global redux store everytime user make changes to url
1821
const url: string = e.target.value;
1922

@@ -24,25 +27,29 @@ const TRPCMethodAndEndpointEntryForm = () => {
2427
})
2528
);
2629
};
27-
2830
return (
29-
<div
30-
className="is-flex is-justify-content-center"
31-
style={{ padding: '10px' }}
32-
>
33-
<div id="tRPCButton" className="no-border-please button is-webrtc">
34-
<span>tRPC</span>
31+
<>
32+
<div
33+
className="is-flex is-justify-content-center"
34+
style={{ padding: '10px' }}
35+
>
36+
<div id="tRPCButton" className="no-border-please button is-webrtc">
37+
<span>tRPC</span>
38+
</div>
39+
<input
40+
className="ml-1 input input-is-medium is-info"
41+
type="text"
42+
value={requestFields.url}
43+
placeholder="Enter your url here"
44+
onChange={(e) => {
45+
urlChangeHandler(e);
46+
}}
47+
/>
3548
</div>
36-
<input
37-
className="ml-1 input input-is-medium is-info"
38-
type="text"
39-
value={requestFields.url}
40-
placeholder="No url needed"
41-
onChange={(e) => {
42-
urlChangeHandler(e);
43-
}}
44-
/>
45-
</div>
49+
{props.warningMessage.uri && (
50+
<div className="warningMessage">{props.warningMessage.uri}</div>
51+
)}
52+
</>
4653
);
4754
};
4855

src/client/components/main/TRPC-composer/TRPCVariableForm.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ export default function TRPCVariableForm(props) {
1818
<div id="gql-var-entry">
1919
<TextCodeArea
2020
mode="application/json"
21-
placeholder="Variable/s for this procedure(objects must be passed in as json format)"
21+
placeholder="Variable/s for this procedure(objects must be passed in as json format string)"
2222
height="50px"
2323
value={props.procedureData.variable}
2424
onChange={onChangeHandler}

src/client/components/main/response-composer/EventsContainer.tsx

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -36,14 +36,19 @@ function EventsContainer({ currentResponse }: EventsContainerProps) {
3636
let responseBody = '';
3737
//check if its a trpc response
3838
if (currentResponse.trpc) {
39-
events.forEach((event: any, idx: number) => {
40-
if (event) {
41-
const eventStr = JSON.stringify(event, null, 4);
42-
responseBody += `-------------${
43-
idx ? 'Mutate Result' : 'Query result'
44-
}-------------\n${eventStr}\n\n`;
45-
}
46-
});
39+
if (currentResponse.connection === 'error') {
40+
const eventStr = JSON.stringify(currentResponse.error, null, 4);
41+
responseBody += `-------------'ERROR'-------------\n${eventStr}\n\n`;
42+
} else {
43+
events.forEach((event: any, idx: number) => {
44+
if (event) {
45+
const eventStr = JSON.stringify(event, null, 4);
46+
responseBody += `-------------${
47+
idx ? 'Mutate Result' : 'Query result'
48+
}-------------\n${eventStr}\n\n`;
49+
}
50+
});
51+
}
4752
}
4853
// If it's a stream or graphQL subscription
4954
else if (

0 commit comments

Comments
 (0)