dce-reactkit 3.8.6 → 3.8.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Send a server-to-server request from this sever to another server that uses
3
+ * dce-reactkit [for server only]
4
+ * @author Gabe Abrams
5
+ * @param opts object containing all arguments
6
+ * @param opts.path - the path of the other server's endpoint
7
+ * @param [opts.method=GET] - the method of the endpoint
8
+ * @param [opts.params] - query/body parameters to include
9
+ * @param [opts.headers] - headers to include
10
+ * @returns response from server
11
+ */
12
+ declare const visitEndpointOnAnotherServer: (opts: {
13
+ path: string;
14
+ method?: "GET" | "POST" | "DELETE" | "PUT" | undefined;
15
+ params?: {
16
+ [x: string]: any;
17
+ } | undefined;
18
+ headers?: {
19
+ [x: string]: any;
20
+ } | undefined;
21
+ }) => Promise<any>;
22
+ export default visitEndpointOnAnotherServer;
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Sends and retries an http request
3
+ * @author Gabriel Abrams
4
+ * @param opts object containing all arguments
5
+ * @param opts.path path to send request to
6
+ * @param [opts.host] host to send request to
7
+ * @param [opts.method=GET] http method to use
8
+ * @param [opts.params] body/data to include in the request
9
+ * @param [opts.headers] headers to include in the request
10
+ * @param [opts.sendCrossDomainCredentials=true if in development mode] if true,
11
+ * send cross-domain credentials even if not in dev mode
12
+ * @param [opts.responseType=JSON] expected response type
13
+ * @returns { body, status, headers } on success
14
+ */
15
+ declare const sendServerToServerRequest: (opts: {
16
+ path: string;
17
+ host?: string | undefined;
18
+ method?: "GET" | "POST" | "DELETE" | "PUT" | undefined;
19
+ params?: {
20
+ [x: string]: any;
21
+ } | undefined;
22
+ headers?: {
23
+ [x: string]: any;
24
+ } | undefined;
25
+ responseType?: "Text" | "JSON" | undefined;
26
+ }) => Promise<{
27
+ body: any;
28
+ status: number;
29
+ headers: {
30
+ [x: string]: any;
31
+ };
32
+ }>;
33
+ export default sendServerToServerRequest;
@@ -15,6 +15,9 @@ declare enum ReactKitErrorCode {
15
15
  NotAdmin = "DRK10",
16
16
  NotAllowedToReviewLogs = "DRK11",
17
17
  ThemeCheckedBeforeReactKitReady = "DRK12",
18
- SessionExpiredMessageGottenBeforeReactKitReady = "DRK13"
18
+ SessionExpiredMessageGottenBeforeReactKitReady = "DRK13",
19
+ NotConnected = "DRK14",
20
+ SelfSigned = "DRK15",
21
+ ResponseParseError = "DRK16"
19
22
  }
20
23
  export default ReactKitErrorCode;
package/dist/index.d.ts CHANGED
@@ -1620,7 +1620,10 @@ declare enum ReactKitErrorCode {
1620
1620
  NotAdmin = "DRK10",
1621
1621
  NotAllowedToReviewLogs = "DRK11",
1622
1622
  ThemeCheckedBeforeReactKitReady = "DRK12",
1623
- SessionExpiredMessageGottenBeforeReactKitReady = "DRK13"
1623
+ SessionExpiredMessageGottenBeforeReactKitReady = "DRK13",
1624
+ NotConnected = "DRK14",
1625
+ SelfSigned = "DRK15",
1626
+ ResponseParseError = "DRK16"
1624
1627
  }
1625
1628
 
1626
1629
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dce-reactkit",
3
- "version": "3.8.6",
3
+ "version": "3.8.7",
4
4
  "description": "Shared components for Harvard DCE apps",
5
5
  "main": "dist/cjs/index.js",
6
6
  "module": "dist/esm/index.js",
@@ -24,6 +24,7 @@
24
24
  },
25
25
  "homepage": "https://github.com/harvard-edtech/dce-reactkit#readme",
26
26
  "dependencies": {
27
+ "qs": "^6.x.x",
27
28
  "react-select": "^5.7.3"
28
29
  },
29
30
  "peerDependencies": {
@@ -0,0 +1,90 @@
1
+ // Import custom error
2
+ import ErrorWithCode from '../../errors/ErrorWithCode';
3
+
4
+ // Import shared types
5
+ import ReactKitErrorCode from '../../types/ReactKitErrorCode';
6
+
7
+ // Import other helpers
8
+ import sendServerToServerRequest from './sendServerToServerRequest';
9
+
10
+ /**
11
+ * Send a server-to-server request from this sever to another server that uses
12
+ * dce-reactkit [for server only]
13
+ * @author Gabe Abrams
14
+ * @param opts object containing all arguments
15
+ * @param opts.path - the path of the other server's endpoint
16
+ * @param [opts.method=GET] - the method of the endpoint
17
+ * @param [opts.params] - query/body parameters to include
18
+ * @param [opts.headers] - headers to include
19
+ * @returns response from server
20
+ */
21
+ const visitEndpointOnAnotherServer = async (
22
+ opts: {
23
+ path: string,
24
+ method?: ('GET' | 'POST' | 'DELETE' | 'PUT'),
25
+ params?: { [key in string]: any },
26
+ headers?: { [k in string]: any },
27
+ },
28
+ ): Promise<any> => {
29
+ // Remove properties with undefined values
30
+ let params: { [key in string]: any } | undefined;
31
+ if (opts.params) {
32
+ params = Object.fromEntries(
33
+ Object
34
+ .entries(opts.params)
35
+ .filter(([, value]) => {
36
+ return value !== undefined;
37
+ }),
38
+ );
39
+ }
40
+
41
+ // Automatically JSONify arrays and objects
42
+ if (params) {
43
+ params = Object.fromEntries(
44
+ Object
45
+ .entries(params)
46
+ .map(([key, value]) => {
47
+ if (Array.isArray(value) || typeof value === 'object') {
48
+ return [key, JSON.stringify(value)];
49
+ }
50
+ return [key, value];
51
+ }),
52
+ );
53
+ }
54
+
55
+ // Send the request
56
+ const response = await sendServerToServerRequest({
57
+ path: opts.path,
58
+ method: opts.method ?? 'GET',
59
+ params,
60
+ });
61
+
62
+ // Check for failure
63
+ if (!response || !response.body) {
64
+ throw new ErrorWithCode(
65
+ 'We didn\'t get a response from the server. Please check your internet connection.',
66
+ ReactKitErrorCode.NoResponse,
67
+ );
68
+ }
69
+ if (!response.body.success) {
70
+ // Other errors
71
+ throw new ErrorWithCode(
72
+ (
73
+ response.body.message
74
+ || 'An unknown error occurred. Please contact an admin.'
75
+ ),
76
+ (
77
+ response.body.code
78
+ || ReactKitErrorCode.NoCode
79
+ ),
80
+ );
81
+ }
82
+
83
+ // Success! Extract the body
84
+ const { body } = response.body;
85
+
86
+ // Return
87
+ return body;
88
+ };
89
+
90
+ export default visitEndpointOnAnotherServer;
@@ -0,0 +1,164 @@
1
+ // Import libs
2
+ import qs from 'qs';
3
+
4
+ // Import shared types
5
+ import ReactKitErrorCode from '../../types/ReactKitErrorCode';
6
+
7
+ // Import custom error
8
+ import ErrorWithCode from '../../errors/ErrorWithCode';
9
+
10
+ /**
11
+ * Sends and retries an http request
12
+ * @author Gabriel Abrams
13
+ * @param opts object containing all arguments
14
+ * @param opts.path path to send request to
15
+ * @param [opts.host] host to send request to
16
+ * @param [opts.method=GET] http method to use
17
+ * @param [opts.params] body/data to include in the request
18
+ * @param [opts.headers] headers to include in the request
19
+ * @param [opts.sendCrossDomainCredentials=true if in development mode] if true,
20
+ * send cross-domain credentials even if not in dev mode
21
+ * @param [opts.responseType=JSON] expected response type
22
+ * @returns { body, status, headers } on success
23
+ */
24
+ const sendServerToServerRequest = async (
25
+ opts: {
26
+ path: string,
27
+ host?: string,
28
+ method?: ('GET' | 'POST' | 'PUT' | 'DELETE'),
29
+ params?: { [k in string]: any },
30
+ headers?: { [k in string]: any },
31
+ responseType?: 'Text' | 'JSON',
32
+ },
33
+ ): Promise<{
34
+ body: any,
35
+ status: number,
36
+ headers: { [k in string]: any },
37
+ }> => {
38
+ // Process method
39
+ const method: ('GET' | 'POST' | 'PUT' | 'DELETE') = (opts.method || 'GET');
40
+
41
+ // Encode objects within params
42
+ let params: {
43
+ [k in string]: any
44
+ } | undefined;
45
+ if (opts.params) {
46
+ params = {};
47
+ Object.entries(opts.params).forEach(([key, val]) => {
48
+ if (typeof val === 'object' && !Array.isArray(val)) {
49
+ (params as any)[key] = JSON.stringify(val);
50
+ } else {
51
+ (params as any)[key] = val;
52
+ }
53
+ });
54
+ }
55
+
56
+ // Stringify parameters
57
+ const stringifiedParams = qs.stringify(params || {}, {
58
+ encodeValuesOnly: true,
59
+ arrayFormat: 'brackets',
60
+ });
61
+
62
+ // Create url (include query if GET)
63
+ const query = (method === 'GET' ? `?${stringifiedParams}` : '');
64
+ let url;
65
+ if (!opts.host) {
66
+ // No host included at all. Just send to a path
67
+ url = `${opts.path}${query}`;
68
+ } else {
69
+ url = `https://${opts.host}${opts.path}${query}`;
70
+ }
71
+
72
+ // Update headers
73
+ const headers = opts.headers || {};
74
+ let data: string | null | { [k: string]: any } | undefined = null;
75
+ if (!headers['Content-Type']) {
76
+ // Form encoded
77
+ headers['Content-Type'] = 'application/x-www-form-urlencoded';
78
+ // Add data if applicable
79
+ data = (method !== 'GET' ? stringifiedParams : null);
80
+ } else {
81
+ // JSON encode
82
+ data = params;
83
+ }
84
+
85
+ // Encode data
86
+ let encodedData: URLSearchParams | string | undefined;
87
+ if (data) {
88
+ if (headers['Content-Type'] === 'application/x-www-form-urlencoded') {
89
+ encodedData = new URLSearchParams(params);
90
+ } else {
91
+ encodedData = JSON.stringify(data);
92
+ }
93
+ }
94
+
95
+ // Send request
96
+ try {
97
+ const response = await fetch(
98
+ url,
99
+ {
100
+ method,
101
+ mode: 'cors',
102
+ headers: headers ?? {},
103
+ body: (
104
+ (method !== 'GET' && encodedData)
105
+ ? encodedData
106
+ : undefined
107
+ ),
108
+ redirect: 'follow',
109
+ },
110
+ );
111
+
112
+ // Get headers map
113
+ const responseHeaders: {
114
+ [k in string]: string
115
+ } = {};
116
+ response.headers.forEach((value, key) => {
117
+ responseHeaders[key] = value;
118
+ });
119
+
120
+ // Process response based on responseType
121
+ try {
122
+ // Parse response
123
+ let responseBody: any;
124
+ if (
125
+ opts.responseType
126
+ && opts.responseType === 'Text'
127
+ ) {
128
+ // Response type is text
129
+ responseBody = await response.text();
130
+ } else {
131
+ // Response type is JSON
132
+ responseBody = await response.json();
133
+ }
134
+
135
+ // Return response
136
+ return {
137
+ body: responseBody,
138
+ status: response.status,
139
+ headers: responseHeaders,
140
+ };
141
+ } catch (err) {
142
+ throw new ErrorWithCode(
143
+ `Failed to parse response as ${opts.responseType}: ${(err as any)?.message}`,
144
+ ReactKitErrorCode.ResponseParseError,
145
+ );
146
+ }
147
+ } catch (err) {
148
+ // Self-signed certificate error:
149
+ if ((err as any)?.message?.includes('self signed certificate')) {
150
+ throw new ErrorWithCode(
151
+ 'We refused to send a request because the receiver has self-signed certificates.',
152
+ ReactKitErrorCode.SelfSigned,
153
+ );
154
+ }
155
+
156
+ // No tries left
157
+ throw new ErrorWithCode(
158
+ `We encountered an error when trying to send a network request. If this issue persists, contact an admin. Error: ${(err as any)?.message}`,
159
+ ReactKitErrorCode.NotConnected,
160
+ );
161
+ }
162
+ };
163
+
164
+ export default sendServerToServerRequest;
@@ -1,4 +1,4 @@
1
- // Highest error code = DRK13
1
+ // Highest error code = DRK16
2
2
 
3
3
  /**
4
4
  * List of error codes built into the react kit
@@ -18,6 +18,11 @@ enum ReactKitErrorCode {
18
18
  NotAllowedToReviewLogs = 'DRK11',
19
19
  ThemeCheckedBeforeReactKitReady = 'DRK12',
20
20
  SessionExpiredMessageGottenBeforeReactKitReady = 'DRK13',
21
+
22
+ // Server-to-server requests
23
+ NotConnected = 'DRK14',
24
+ SelfSigned = 'DRK15',
25
+ ResponseParseError = 'DRK16',
21
26
  }
22
27
 
23
28
  export default ReactKitErrorCode;