dce-reactkit 3.12.1-beta-cross-server.2 → 3.12.3

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.
Files changed (51) hide show
  1. package/.vscode/settings.json +3 -1
  2. package/README.md +0 -20
  3. package/dist/cjs/index.js +356 -406
  4. package/dist/cjs/index.js.map +1 -1
  5. package/dist/cjs/types/components/ItemPicker/index.d.ts +1 -0
  6. package/dist/cjs/types/components/Modal/ModalProps.d.ts +2 -0
  7. package/dist/cjs/types/components/TabBox.d.ts +1 -0
  8. package/dist/cjs/types/helpers/genRouteHandler.d.ts +10 -10
  9. package/dist/cjs/types/helpers/getWordCount.d.ts +10 -0
  10. package/dist/cjs/types/helpers/visitEndpointOnAnotherServer/index.d.ts +13 -15
  11. package/dist/cjs/types/helpers/visitEndpointOnAnotherServer/sendServerToServerRequest.d.ts +6 -0
  12. package/dist/cjs/types/index.d.ts +2 -3
  13. package/dist/cjs/types/server/initServer.d.ts +0 -11
  14. package/dist/cjs/types/types/ReactKitErrorCode.d.ts +1 -13
  15. package/dist/esm/index.js +357 -406
  16. package/dist/esm/index.js.map +1 -1
  17. package/dist/esm/types/components/ItemPicker/index.d.ts +1 -0
  18. package/dist/esm/types/components/Modal/ModalProps.d.ts +2 -0
  19. package/dist/esm/types/components/TabBox.d.ts +1 -0
  20. package/dist/esm/types/helpers/genRouteHandler.d.ts +10 -10
  21. package/dist/esm/types/helpers/getWordCount.d.ts +10 -0
  22. package/dist/esm/types/helpers/visitEndpointOnAnotherServer/index.d.ts +13 -15
  23. package/dist/esm/types/helpers/visitEndpointOnAnotherServer/sendServerToServerRequest.d.ts +6 -0
  24. package/dist/esm/types/index.d.ts +2 -3
  25. package/dist/esm/types/server/initServer.d.ts +0 -11
  26. package/dist/esm/types/types/ReactKitErrorCode.d.ts +1 -13
  27. package/dist/index.d.ts +39 -62
  28. package/package.json +2 -5
  29. package/src/components/IntelliTable.tsx +1 -1
  30. package/src/components/ItemPicker/NestableItemList.tsx +1 -0
  31. package/src/components/ItemPicker/index.tsx +96 -0
  32. package/src/components/Modal/ModalProps.ts +4 -0
  33. package/src/components/Modal/index.tsx +59 -20
  34. package/src/components/TabBox.tsx +27 -9
  35. package/src/helpers/genRouteHandler.ts +95 -55
  36. package/src/helpers/getWordCount.ts +26 -0
  37. package/src/helpers/visitEndpointOnAnotherServer/index.ts +41 -42
  38. package/src/helpers/visitEndpointOnAnotherServer/sendServerToServerRequest.ts +5 -3
  39. package/src/index.ts +2 -4
  40. package/src/server/initServer.ts +0 -18
  41. package/src/types/ReactKitErrorCode.tsx +1 -13
  42. package/dist/cjs/types/helpers/dataSigner.d.ts +0 -38
  43. package/dist/cjs/types/server/initCrossServerCredentialCollection.d.ts +0 -8
  44. package/dist/cjs/types/types/CrossServerCredential.d.ts +0 -11
  45. package/dist/esm/types/helpers/dataSigner.d.ts +0 -38
  46. package/dist/esm/types/server/initCrossServerCredentialCollection.d.ts +0 -8
  47. package/dist/esm/types/types/CrossServerCredential.d.ts +0 -11
  48. package/genEncodedSecret.ts +0 -28
  49. package/src/helpers/dataSigner.ts +0 -232
  50. package/src/server/initCrossServerCredentialCollection.ts +0 -16
  51. package/src/types/CrossServerCredential.ts +0 -16
@@ -14,6 +14,9 @@ import React, { useState, useEffect, useRef } from 'react';
14
14
  import ReactDOM from 'react-dom';
15
15
 
16
16
  // Import other components
17
+ import LoadingSpinner from '../LoadingSpinner';
18
+
19
+ // Import helpers
17
20
  import waitMs from '../../helpers/waitMs';
18
21
 
19
22
  // Import types
@@ -41,6 +44,7 @@ const BASE_Z_INDEX_ON_TOP = 2000000000;
41
44
 
42
45
  // Constants
43
46
  const MS_TO_ANIMATE = 200; // Animation duration
47
+ const MS_TO_WAIT_BEFORE_SHOWING_LOADING_INDICATOR = 1000;
44
48
 
45
49
  // Modal type to list of buttons
46
50
  const modalTypeToModalButtonTypes: {
@@ -256,6 +260,8 @@ const Modal: React.FC<ModalProps> = (props) => {
256
260
  dontAllowBackdropExit,
257
261
  dontShowXButton,
258
262
  onTopOfOtherModals,
263
+ isLoading,
264
+ isLoadingCancelable,
259
265
  } = props;
260
266
 
261
267
  // Determine if no header either
@@ -266,6 +272,7 @@ const Modal: React.FC<ModalProps> = (props) => {
266
272
  // True if animation is in use
267
273
  const [animatingIn, setAnimatingIn] = useState(true);
268
274
  const [animatingPop, setAnimatingPop] = useState(false);
275
+ const [showModal, setShowModal] = useState(false);
269
276
 
270
277
  /* -------------- Refs -------------- */
271
278
 
@@ -298,6 +305,15 @@ const Modal: React.FC<ModalProps> = (props) => {
298
305
  if (mounted.current) {
299
306
  setAnimatingIn(false);
300
307
  }
308
+ if (isLoading) {
309
+ // wait before showing modal
310
+ await waitMs(MS_TO_WAIT_BEFORE_SHOWING_LOADING_INDICATOR);
311
+ if (mounted.current) {
312
+ setShowModal(true);
313
+ }
314
+ } else {
315
+ setShowModal(true);
316
+ }
301
317
  })();
302
318
 
303
319
  return () => {
@@ -400,6 +416,17 @@ const Modal: React.FC<ModalProps> = (props) => {
400
416
  animationClass = 'Modal-animating-pop';
401
417
  }
402
418
 
419
+ // default to show close button when not loading and not show close button when loading
420
+ // unless downShowXButton or isLoadingCancelable
421
+ const showCloseButton = (
422
+ // The modal must have an onClose function
423
+ onClose
424
+ // ...and the close button is allowed to be visible
425
+ && !dontShowXButton
426
+ // ...and should not be shown if the modal is loading and not cancelable
427
+ && !(isLoading && !isLoadingCancelable)
428
+ );
429
+
403
430
  // Render the modal
404
431
  const contentToRender = (
405
432
  <div
@@ -436,6 +463,8 @@ const Modal: React.FC<ModalProps> = (props) => {
436
463
  handleClose(ModalButtonType.Cancel);
437
464
  }}
438
465
  />
466
+
467
+ {showModal && (
439
468
  <div
440
469
  className={`modal-dialog modal-${size} ${animationClass} modal-dialog-scrollable modal-dialog-centered`}
441
470
  style={{
@@ -498,7 +527,7 @@ const Modal: React.FC<ModalProps> = (props) => {
498
527
  {title}
499
528
  </h5>
500
529
 
501
- {(onClose && !dontShowXButton) && (
530
+ {showCloseButton && (
502
531
  <button
503
532
  type="button"
504
533
  className="Modal-x-button btn-close"
@@ -518,25 +547,34 @@ const Modal: React.FC<ModalProps> = (props) => {
518
547
  )}
519
548
  </div>
520
549
  )}
521
- {children && (
522
- <div
523
- className="modal-body"
524
- style={{
525
- color: (
526
- isDarkModeOn()
527
- ? 'white'
528
- : undefined
529
- ),
530
- backgroundColor: (
531
- isDarkModeOn()
532
- ? '#444'
533
- : undefined
534
- ),
535
- }}
536
- >
537
- {children}
538
- </div>
539
- )}
550
+
551
+ <div
552
+ className="modal-body"
553
+ style={{
554
+ color: (
555
+ isDarkModeOn()
556
+ ? 'white'
557
+ : undefined
558
+ ),
559
+ backgroundColor: (
560
+ isDarkModeOn()
561
+ ? '#444'
562
+ : undefined
563
+ ),
564
+ }}
565
+ >
566
+ {
567
+ isLoading
568
+ ? (
569
+ <>
570
+ <LoadingSpinner />
571
+ <span className="sr-only">Content loading</span>
572
+ </>
573
+ )
574
+ : children
575
+ }
576
+ </div>
577
+
540
578
  {footer && (
541
579
  <div
542
580
  className="modal-footer pt-1 pb-1"
@@ -563,6 +601,7 @@ const Modal: React.FC<ModalProps> = (props) => {
563
601
  )}
564
602
  </div>
565
603
  </div>
604
+ )}
566
605
  </div>
567
606
  );
568
607
 
@@ -15,6 +15,8 @@ type Props = {
15
15
  title: React.ReactNode,
16
16
  // Children/contents inside the box
17
17
  children: React.ReactNode,
18
+ // Children to display on the top right of the tab box
19
+ topRightChildren?: React.ReactNode,
18
20
  // If true, don't add margin below the tab box
19
21
  noBottomMargin?: boolean,
20
22
  // If true, don't add padding to bottom of tab box
@@ -49,7 +51,9 @@ const style = `
49
51
 
50
52
  /* Container for Title */
51
53
  .TabBox-title-container {
52
- /* Place on Left */
54
+ display: flex;
55
+ justify-content: space-between;
56
+ align-items: center;
53
57
  position: relative;
54
58
  left: 0;
55
59
  text-align: left;
@@ -82,6 +86,19 @@ const style = `
82
86
  background: #fdfdfd;
83
87
  }
84
88
 
89
+ .TabBox-title-right-container {
90
+ display: flex;
91
+ flex-direction: row;
92
+ align-items: bottom;
93
+ height: 2.4rem;
94
+ overflow: visible;
95
+ }
96
+
97
+ .TabBox-title-right-contents {
98
+ margin-right: 0.5rem;
99
+ margin-bottom: 0.2rem;
100
+ }
101
+
85
102
  /* Make the TabBox's Children Appear Above Title if Overlap Occurs */
86
103
  .TabBox-children {
87
104
  position: relative;
@@ -98,11 +115,10 @@ const TabBox: React.FC<Props> = (props) => {
98
115
  /* -------------------------------- Setup ------------------------------- */
99
116
  /*------------------------------------------------------------------------*/
100
117
 
101
- /* -------------- Props ------------- */
102
-
103
118
  const {
104
119
  title,
105
120
  children,
121
+ topRightChildren,
106
122
  noBottomPadding,
107
123
  noBottomMargin,
108
124
  } = props;
@@ -111,21 +127,23 @@ const TabBox: React.FC<Props> = (props) => {
111
127
  /* ------------------------------- Render ------------------------------- */
112
128
  /*------------------------------------------------------------------------*/
113
129
 
114
- /*----------------------------------------*/
115
- /* --------------- Main UI -------------- */
116
- /*----------------------------------------*/
117
-
118
- // Full UI
119
130
  return (
120
131
  <div className={`TabBox-container ${noBottomMargin ? '' : 'mb-2'}`}>
121
132
  {/* Style */}
122
133
  <style>{style}</style>
123
134
 
124
- {/* Title */}
135
+ {/* Title Row with Left and Right sections */}
125
136
  <div className="TabBox-title-container">
126
137
  <div className="TabBox-title">
127
138
  {title}
128
139
  </div>
140
+ {topRightChildren && (
141
+ <div className="TabBox-title-right-container">
142
+ <div className="TabBox-title-right-contents">
143
+ {topRightChildren}
144
+ </div>
145
+ </div>
146
+ )}
129
147
  </div>
130
148
 
131
149
  {/* Contents */}
@@ -17,7 +17,6 @@ import genErrorPage from '../html/genErrorPage';
17
17
  import genInfoPage from '../html/genInfoPage';
18
18
  import parseUserAgent from './parseUserAgent';
19
19
  import getTimeInfoInET from './getTimeInfoInET';
20
- import { parseSignedPack } from './dataSigner';
21
20
 
22
21
  // Import shared types
23
22
  import LogFunction from '../types/LogFunction';
@@ -30,7 +29,6 @@ import LogSourceSpecificInfo from '../types/Log/LogSourceSpecificInfo';
30
29
  import LogBuiltInMetadata from '../types/LogBuiltInMetadata';
31
30
  import LogAction from '../types/LogAction';
32
31
  import LogLevel from '../types/LogLevel';
33
- import ErrorWithCode from '../errors/ErrorWithCode';
34
32
 
35
33
  /**
36
34
  * Generate an express API route handler
@@ -39,15 +37,14 @@ import ErrorWithCode from '../errors/ErrorWithCode';
39
37
  * @param opts.paramTypes map containing the types for each parameter that is
40
38
  * included in the request (map: param name => type)
41
39
  * @param opts.handler function that processes the request
42
- * @param [opts.crossServerScope] the scope associated with this endpoint.
43
- * If defined, this is a cross-server endpoint, which will never
44
- * have any launch data, will never check Canvas roles or launch status, and will
45
- * instead use scopes and reactkit credentials to sign and validate requests.
46
- * Never start the path with /api/ttm or /api/admin if the endpoint is a cross-server
47
- * endpoint because those roles will not be validated
48
- * @param [opts.skipSessionCheck=true if crossServerScope defined] if true, skip
49
- * the session check (allow users to not be logged in and launched via LTI).
50
- * If crossServerScope is defined, this is always true
40
+ * @param [opts.skipSessionCheck] if true, skip the session check (allow users
41
+ * to not be logged in and launched via LTI)
42
+ * @param [opts.allowedHosts] if included, only allow requests from these hosts
43
+ * (start a hostname with a "*" to only check the end of the hostname)
44
+ * you can include just one string instead of an array
45
+ * @param [opts.bannedHosts] if included, do not allow requests from these hosts
46
+ * (start a hostname with a "*" to only check the end of the hostname)
47
+ * you can include just one string instead of an array
51
48
  * @param [opts.unhandledErrorMessagePrefix] if included, when an error that
52
49
  * is not of type ErrorWithCode is thrown, the client will receive an error
53
50
  * where the error message is prefixed with this string. For example,
@@ -110,67 +107,106 @@ const genRouteHandler = (
110
107
  logServerEvent: LogFunction,
111
108
  },
112
109
  ) => any,
113
- crossServerScope?: string,
114
110
  skipSessionCheck?: boolean,
111
+ allowedHosts?: string[] | string,
112
+ bannedHosts?: string[] | string,
115
113
  unhandledErrorMessagePrefix?: string,
116
114
  },
117
115
  ) => {
118
116
  // Return a route handler
119
117
  return async (req: any, res: any, next: () => void) => {
120
- /*----------------------------------------*/
121
- /* ------------- Preparation ------------ */
122
- /*----------------------------------------*/
123
-
124
118
  // Output params
125
119
  const output: { [k in string]: any } = {};
126
120
 
127
- // Determine cross server scopes
128
- let crossServerScope: string | null = null;
129
- if (opts.crossServerScope) {
130
- crossServerScope = opts.crossServerScope ?? null;
131
- }
121
+ /*----------------------------------------*/
122
+ /* ----------- Hostname Check ----------- */
123
+ /*----------------------------------------*/
132
124
 
133
- // Determine whether we're skipping the session check
134
- const skipSessionCheck = !!(
135
- opts.skipSessionCheck
136
- || crossServerScope
125
+ // Get hostnames
126
+ const originURL = String(
127
+ req.get('origin')
128
+ || req.headers.origin
129
+ || req.headers.referer,
137
130
  );
131
+ const originHostname = (
132
+ originURL
133
+ // Remove protocol
134
+ .replace(/(^\w+:|^)\/\//, '')
135
+ // Remove port
136
+ .replace(/:\d+$/, '')
137
+ );
138
+ const serverHostname = String(req.hostname);
139
+
140
+ // Check allowed
141
+ if (opts.allowedHosts) {
142
+ // Only accept requests from allowed hosts
143
+ const allowedArray = (
144
+ Array.isArray(opts.allowedHosts)
145
+ ? opts.allowedHosts
146
+ : [opts.allowedHosts]
147
+ );
138
148
 
139
- // Get body from everywhere it can come from
140
- let requestBody: {
141
- [k: string]: any,
142
- } = {
143
- ...req.body,
144
- ...req.query,
145
- ...req.params,
146
- };
149
+ // Check if server is localhost
150
+ if (serverHostname === 'localhost') {
151
+ // Allow localhost
152
+ allowedArray.push('localhost');
153
+ }
147
154
 
148
- /*----------------------------------------*/
149
- /* ------- Cross-Server Validation ------ */
150
- /*----------------------------------------*/
155
+ // Check if current host is allowed
156
+ const allowed = allowedArray.some((allowedHost) => {
157
+ if (allowedHost.startsWith('*')) {
158
+ // Check end of hostname
159
+ return originHostname.endsWith(allowedHost.substring(1));
160
+ }
151
161
 
152
- if (crossServerScope) {
153
- // Get the signed pack
154
- const { signedPack } = requestBody;
162
+ // Check full hostname
163
+ return originHostname.toLowerCase() === allowedHost.toLowerCase();
164
+ });
155
165
 
156
- // If no pack, throw error
157
- if (!signedPack || typeof signedPack !== 'string') {
158
- throw new ErrorWithCode(
159
- 'Could not process a cross server request because there was no valid signed pack.',
160
- ReactKitErrorCode.CrossServerNoPack,
166
+ // If not allowed, return error
167
+ if (!allowed) {
168
+ return handleError(
169
+ res,
170
+ {
171
+ message: 'You are not allowed to access this endpoint.',
172
+ code: ReactKitErrorCode.HostNotAllowed,
173
+ status: 403,
174
+ },
161
175
  );
162
176
  }
177
+ }
178
+
179
+ // Check banned
180
+ if (opts.bannedHosts) {
181
+ // Do not allow requests from banned hosts
182
+ const bannedArray = (
183
+ Array.isArray(opts.bannedHosts)
184
+ ? opts.bannedHosts
185
+ : [opts.bannedHosts]
186
+ );
187
+
188
+ // Check if current host is banned
189
+ const banned = bannedArray.some((bannedHost) => {
190
+ if (bannedHost.startsWith('*')) {
191
+ // Check end of hostname
192
+ return originHostname.endsWith(bannedHost.substring(1));
193
+ }
163
194
 
164
- // Validate the request
165
- const crossServerParams = await parseSignedPack({
166
- method: req.method ?? 'GET',
167
- path: req.path,
168
- scope: crossServerScope,
169
- signedPack,
195
+ // Check full hostname
196
+ return originHostname.toLowerCase() === bannedHost.toLowerCase();
170
197
  });
171
198
 
172
- // Replace body with params from the pack
173
- requestBody = crossServerParams ?? {};
199
+ // If banned, return error
200
+ if (banned) {
201
+ return handleError(
202
+ res,
203
+ {
204
+ message: 'You are not allowed to access this endpoint.',
205
+ code: ReactKitErrorCode.HostBanned,
206
+ status: 403,
207
+ },
208
+ );
209
+ }
174
210
  }
175
211
 
176
212
  /*----------------------------------------*/
@@ -183,7 +219,11 @@ const genRouteHandler = (
183
219
  const [name, type] = paramList[i];
184
220
 
185
221
  // Find the value as a string
186
- const value = requestBody[name];
222
+ const value = (
223
+ req.params[name]
224
+ || req.query[name]
225
+ || req.body[name]
226
+ );
187
227
 
188
228
  // Parse
189
229
  if (type === ParamType.Boolean || type === ParamType.BooleanOptional) {
@@ -366,7 +406,7 @@ const genRouteHandler = (
366
406
  // Not launched
367
407
  (!launched || !launchInfo)
368
408
  // Not skipping the session check
369
- && !skipSessionCheck
409
+ && !opts.skipSessionCheck
370
410
  ) {
371
411
  return handleError(
372
412
  res,
@@ -397,7 +437,7 @@ const genRouteHandler = (
397
437
  )
398
438
  )
399
439
  // Not skipping the session check
400
- && !skipSessionCheck
440
+ && !opts.skipSessionCheck
401
441
  ) {
402
442
  return handleError(
403
443
  res,
@@ -0,0 +1,26 @@
1
+ const punctuationRegex = /[!@#$%^&*(),.?\/;:'"\-\[\]]/g;
2
+
3
+ /**
4
+ * Get number of words in string
5
+ * @author Gardenia Liu
6
+ * @author Allison Zhang
7
+ * @author Gabe Abrams
8
+ * @param text the string to check
9
+ * @returns number of words in the string
10
+ */
11
+ const getWordCount = (text: string): number => {
12
+ const trimmedTextWithoutPunctuation = (
13
+ text
14
+ // Remove leading and trailing whitespace
15
+ .trim()
16
+ // Remove punctuation
17
+ .replace(punctuationRegex, '')
18
+ );
19
+ if (trimmedTextWithoutPunctuation.length === 0) {
20
+ return 0;
21
+ }
22
+
23
+ return trimmedTextWithoutPunctuation.split(/\s+/g).length;
24
+ };
25
+
26
+ export default getWordCount;
@@ -1,72 +1,71 @@
1
- // Import data signer
2
- import { createSignedPack } from '../dataSigner';
1
+ // Import custom error
2
+ import ErrorWithCode from '../../errors/ErrorWithCode';
3
3
 
4
4
  // Import shared types
5
- import ErrorWithCode from '../../errors/ErrorWithCode';
6
5
  import ReactKitErrorCode from '../../types/ReactKitErrorCode';
6
+
7
+ // Import other helpers
7
8
  import sendServerToServerRequest from './sendServerToServerRequest';
8
9
 
9
10
  /**
10
- * Visit an endpoint on another server
11
+ * Send a server-to-server request from this sever to another server that uses
12
+ * dce-reactkit [for server only]
11
13
  * @author Gabe Abrams
12
14
  * @param opts object containing all arguments
13
- * @param opts.method the method of the endpoint
14
- * @param opts.path the path of the other server's endpoint
15
- * @param opts.host the host of the other server
16
- * @param [opts.key=process.env.REACTKIT_CROSS_SERVER_CREDENTIAL_KEY] reactkit cross-server
17
- * credential key
18
- * @param [opts.secret=process.env.REACTKIT_CROSS_SERVER_CREDENTIAL_SECRET reactkit cross-server
19
- * credential secret
20
- * @param [opts.params={}] query/body parameters to include
21
- * @param [opts.responseType=JSON] the response type from the other server
15
+ * @param opts.host - the host of the other server
16
+ * @param opts.path - the path of the other server's endpoint
17
+ * @param [opts.method=GET] - the method of the endpoint
18
+ * @param [opts.params] - query/body parameters to include
19
+ * @param [opts.headers] - headers to include
20
+ * @returns response from server
22
21
  */
23
22
  const visitEndpointOnAnotherServer = async (
24
23
  opts: {
25
- method: 'GET' | 'POST' | 'DELETE' | 'PUT',
26
- path: string,
27
24
  host: string,
28
- key?: string,
29
- secret?: string,
25
+ path: string,
26
+ method?: ('GET' | 'POST' | 'DELETE' | 'PUT'),
30
27
  params?: { [key in string]: any },
31
- responseType?: 'JSON' | 'Text',
28
+ headers?: { [k in string]: any },
32
29
  },
33
30
  ): Promise<any> => {
34
- // Get cross-server credentials
35
- const key = opts.key ?? process.env.REACTKIT_CROSS_SERVER_KEY;
36
- const secret = opts.secret ?? process.env.REACTKIT_CROSS_SERVER_SECRET;
37
-
38
- // Throw error if no credentials
39
- if (!key || !secret) {
40
- throw new ErrorWithCode(
41
- 'Cannot send cross-server signed request because either or both the key and secret were not included or found in env.',
42
- ReactKitErrorCode.CrossServerNoCredentialsToSignWith,
31
+ // Remove properties with undefined values
32
+ let params: { [key in string]: any } | undefined;
33
+ if (opts.params) {
34
+ params = Object.fromEntries(
35
+ Object
36
+ .entries(opts.params)
37
+ .filter(([, value]) => {
38
+ return value !== undefined;
39
+ }),
43
40
  );
44
41
  }
45
42
 
46
- // Create signed pack
47
- const signedPack = createSignedPack({
48
- method: opts.method,
49
- path: opts.path,
50
- params: opts.params ?? {},
51
- key,
52
- secret,
53
- });
43
+ // Automatically JSONify arrays and objects
44
+ if (params) {
45
+ params = Object.fromEntries(
46
+ Object
47
+ .entries(params)
48
+ .map(([key, value]) => {
49
+ if (Array.isArray(value) || typeof value === 'object') {
50
+ return [key, JSON.stringify(value)];
51
+ }
52
+ return [key, value];
53
+ }),
54
+ );
55
+ }
54
56
 
55
57
  // Send the request
56
58
  const response = await sendServerToServerRequest({
57
- path: opts.path,
58
59
  host: opts.host,
59
- method: opts.method,
60
- params: {
61
- signedPack,
62
- },
63
- responseType: opts.responseType,
60
+ path: opts.path,
61
+ method: opts.method ?? 'GET',
62
+ params,
64
63
  });
65
64
 
66
65
  // Check for failure
67
66
  if (!response || !response.body) {
68
67
  throw new ErrorWithCode(
69
- 'We didn\'t get a response from the other server. Please check your internet connection.',
68
+ 'We didn\'t get a response from the server. Please check your internet connection.',
70
69
  ReactKitErrorCode.NoResponse,
71
70
  );
72
71
  }
@@ -15,6 +15,9 @@ import ErrorWithCode from '../../errors/ErrorWithCode';
15
15
  * @param [opts.host] host to send request to
16
16
  * @param [opts.method=GET] http method to use
17
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
18
21
  * @param [opts.responseType=JSON] expected response type
19
22
  * @returns { body, status, headers } on success
20
23
  */
@@ -24,6 +27,7 @@ const sendServerToServerRequest = async (
24
27
  host?: string,
25
28
  method?: ('GET' | 'POST' | 'PUT' | 'DELETE'),
26
29
  params?: { [k in string]: any },
30
+ headers?: { [k in string]: any },
27
31
  responseType?: 'Text' | 'JSON',
28
32
  },
29
33
  ): Promise<{
@@ -66,9 +70,7 @@ const sendServerToServerRequest = async (
66
70
  }
67
71
 
68
72
  // Update headers
69
- const headers: {
70
- [k: string]: any,
71
- } = {};
73
+ const headers = opts.headers || {};
72
74
  let data: string | null | { [k: string]: any } | undefined = null;
73
75
  if (!headers['Content-Type']) {
74
76
  // Form encoded
package/src/index.ts CHANGED
@@ -70,7 +70,6 @@ import onlyKeepLetters from './helpers/onlyKeepLetters';
70
70
  import parallelLimit from './helpers/parallelLimit';
71
71
  import logClientEvent, { setClientEventMetadataPopulator } from './helpers/logClientEvent';
72
72
  import initLogCollection from './server/initLogCollection';
73
- import initCrossServerCredentialCollection from './server/initCrossServerCredentialCollection';
74
73
  import getMonthName from './helpers/getMonthName';
75
74
  import genCSV from './helpers/genCSV';
76
75
  import canReviewLogs from './helpers/canReviewLogs';
@@ -96,6 +95,7 @@ import someAsync from './helpers/asyncArrayFunctions/someAsync';
96
95
  import capitalize from './helpers/capitalize';
97
96
  import shuffleArray from './helpers/shuffleArray';
98
97
  import visitEndpointOnAnotherServer from './helpers/visitEndpointOnAnotherServer';
98
+ import getWordCount from './helpers/getWordCount';
99
99
 
100
100
  // Import types
101
101
  import ModalButtonType from './types/ModalButtonType';
@@ -114,7 +114,6 @@ import LogMetadataType from './types/LogMetadataType';
114
114
  import LogFunction from './types/LogFunction';
115
115
  import IntelliTableColumn from './types/IntelliTableColumn';
116
116
  import DropdownItemType from './types/DropdownItemType';
117
- import CrossServerCredential from './types/CrossServerCredential';
118
117
 
119
118
  // Component-specific-types
120
119
  import PickableItem from './components/ItemPicker/types/PickableItem';
@@ -203,6 +202,7 @@ export {
203
202
  someAsync,
204
203
  capitalize,
205
204
  shuffleArray,
205
+ getWordCount,
206
206
  // Client helpers
207
207
  initClient,
208
208
  visitServerEndpoint,
@@ -218,7 +218,6 @@ export {
218
218
  handleError,
219
219
  handleSuccess,
220
220
  initLogCollection,
221
- initCrossServerCredentialCollection,
222
221
  addDBEditorEndpoints,
223
222
  visitEndpointOnAnotherServer,
224
223
  // Types
@@ -237,7 +236,6 @@ export {
237
236
  LogFunction,
238
237
  IntelliTableColumn,
239
238
  DropdownItemType,
240
- CrossServerCredential,
241
239
  // Component-specific-types
242
240
  PickableItem,
243
241
  DBEntry,