dce-reactkit 3.1.20 → 3.2.0-beta.2

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 (52) hide show
  1. package/dist/cjs/index.js +405 -19
  2. package/dist/cjs/index.js.map +1 -1
  3. package/dist/cjs/types/constants/LOG_ROUTE_PATH.d.ts +6 -0
  4. package/dist/cjs/types/constants/ROUTE_PATH_PREFIX.d.ts +6 -0
  5. package/dist/cjs/types/helpers/genRouteHandler.d.ts +2 -0
  6. package/dist/cjs/types/helpers/logClientEvent.d.ts +7 -0
  7. package/dist/cjs/types/helpers/parseUserAgent.d.ts +17 -0
  8. package/dist/cjs/types/index.d.ts +6 -1
  9. package/dist/cjs/types/server/initServer.d.ts +13 -0
  10. package/dist/cjs/types/types/Log/LogMainInfo.d.ts +37 -0
  11. package/dist/cjs/types/types/Log/LogSourceSpecificInfo.d.ts +13 -0
  12. package/dist/cjs/types/types/Log/LogTypeSpecificInfo.d.ts +17 -0
  13. package/dist/cjs/types/types/Log/index.d.ts +10 -0
  14. package/dist/cjs/types/types/LogAction.d.ts +22 -0
  15. package/dist/cjs/types/types/LogFunction.d.ts +24 -0
  16. package/dist/cjs/types/types/LogSource.d.ts +9 -0
  17. package/dist/cjs/types/types/LogType.d.ts +9 -0
  18. package/dist/esm/index.js +402 -20
  19. package/dist/esm/index.js.map +1 -1
  20. package/dist/esm/types/constants/LOG_ROUTE_PATH.d.ts +6 -0
  21. package/dist/esm/types/constants/ROUTE_PATH_PREFIX.d.ts +6 -0
  22. package/dist/esm/types/helpers/genRouteHandler.d.ts +2 -0
  23. package/dist/esm/types/helpers/logClientEvent.d.ts +7 -0
  24. package/dist/esm/types/helpers/parseUserAgent.d.ts +17 -0
  25. package/dist/esm/types/index.d.ts +6 -1
  26. package/dist/esm/types/server/initServer.d.ts +13 -0
  27. package/dist/esm/types/types/Log/LogMainInfo.d.ts +37 -0
  28. package/dist/esm/types/types/Log/LogSourceSpecificInfo.d.ts +13 -0
  29. package/dist/esm/types/types/Log/LogTypeSpecificInfo.d.ts +17 -0
  30. package/dist/esm/types/types/Log/index.d.ts +10 -0
  31. package/dist/esm/types/types/LogAction.d.ts +22 -0
  32. package/dist/esm/types/types/LogFunction.d.ts +24 -0
  33. package/dist/esm/types/types/LogSource.d.ts +9 -0
  34. package/dist/esm/types/types/LogType.d.ts +9 -0
  35. package/dist/index.d.ts +147 -1
  36. package/package.json +1 -1
  37. package/src/components/AppWrapper.tsx +1 -1
  38. package/src/constants/LOG_ROUTE_PATH.ts +9 -0
  39. package/src/constants/ROUTE_PATH_PREFIX.ts +7 -0
  40. package/src/helpers/genRouteHandler.ts +137 -1
  41. package/src/helpers/logClientEvent.tsx +58 -0
  42. package/src/helpers/parseUserAgent.ts +108 -0
  43. package/src/index.ts +10 -0
  44. package/src/server/initServer.ts +90 -0
  45. package/src/types/Log/LogMainInfo.ts +64 -0
  46. package/src/types/Log/LogSourceSpecificInfo.ts +25 -0
  47. package/src/types/Log/LogTypeSpecificInfo.ts +33 -0
  48. package/src/types/Log/index.ts +17 -0
  49. package/src/types/LogAction.ts +38 -0
  50. package/src/types/LogFunction.ts +41 -0
  51. package/src/types/LogSource.ts +12 -0
  52. package/src/types/LogType.ts +12 -0
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Type of a log event
3
+ * @author Gabe Abrams
4
+ */
5
+ declare enum LogType {
6
+ Action = "action",
7
+ Error = "error"
8
+ }
9
+ export default LogType;
package/dist/index.d.ts CHANGED
@@ -515,6 +515,139 @@ declare enum ParamType {
515
515
  StringOptional = "string-optional"
516
516
  }
517
517
 
518
+ /**
519
+ * Main information in a log event
520
+ * @author Gabe Abrams
521
+ */
522
+ declare type LogMainInfo = {
523
+ id: string;
524
+ userFirstName: string;
525
+ userLastName: string;
526
+ userEmail: string;
527
+ userId: number;
528
+ isLearner: boolean;
529
+ isAdmin: boolean;
530
+ isTTM: boolean;
531
+ courseId: number;
532
+ courseName: string;
533
+ browser: {
534
+ name: string;
535
+ version: string;
536
+ };
537
+ device: {
538
+ os: string;
539
+ isMobile: boolean;
540
+ };
541
+ year: number;
542
+ month: number;
543
+ day: number;
544
+ hour: number;
545
+ minute: number;
546
+ timestamp: number;
547
+ category: string;
548
+ subcategory: string;
549
+ tags: string[];
550
+ metadata?: {
551
+ [k: string]: any;
552
+ };
553
+ };
554
+
555
+ /**
556
+ * Source of a log event
557
+ * @author Gabe Abrams
558
+ */
559
+ declare enum LogSource {
560
+ Client = "client",
561
+ Server = "server"
562
+ }
563
+
564
+ /**
565
+ * Log info that is specific to the type of source
566
+ * @author Gabe Abrams
567
+ */
568
+ declare type LogSourceSpecificInfo = ({
569
+ source: LogSource.Client;
570
+ } | {
571
+ source: LogSource.Server;
572
+ routePath: string;
573
+ routeTemplate: string;
574
+ });
575
+
576
+ /**
577
+ * Types of actions
578
+ * @author Gabe Abrams
579
+ */
580
+ declare enum LogAction {
581
+ Open = "open",
582
+ Close = "close",
583
+ Expand = "expand",
584
+ Collapse = "collapse",
585
+ View = "view",
586
+ Interrupt = "interrupt",
587
+ Create = "create",
588
+ Edit = "edit",
589
+ Delete = "delete",
590
+ Add = "add",
591
+ Remove = "remove",
592
+ Activate = "activate",
593
+ Deactivate = "deactivate",
594
+ Peek = "peek",
595
+ Unknown = "unknown"
596
+ }
597
+
598
+ /**
599
+ * Type of a log event
600
+ * @author Gabe Abrams
601
+ */
602
+ declare enum LogType {
603
+ Action = "action",
604
+ Error = "error"
605
+ }
606
+
607
+ /**
608
+ * Log info that is specific to the type of log
609
+ * @author Gabe Abrams
610
+ */
611
+ declare type LogTypeSpecificInfo = ({
612
+ type: LogType.Error;
613
+ errorMessage: string;
614
+ errorCode: string;
615
+ errorStack: string;
616
+ } | {
617
+ type: LogType.Action;
618
+ target: string;
619
+ action: LogAction;
620
+ });
621
+
622
+ /**
623
+ * A single log event corresponding to an action performed by a user or an
624
+ * error encountered by a user
625
+ * @author Gabe Abrams
626
+ */
627
+ declare type Log = (LogMainInfo & LogSourceSpecificInfo & LogTypeSpecificInfo);
628
+
629
+ /**
630
+ * Type of a log action function
631
+ * @author Gabe Abrams
632
+ */
633
+ declare type LogFunction = (opts: ({
634
+ category: string | {
635
+ name: string;
636
+ };
637
+ subcategory?: string | {
638
+ name: string;
639
+ };
640
+ tags?: string[];
641
+ metadata?: {
642
+ [k: string]: any;
643
+ };
644
+ } & ({
645
+ error: any;
646
+ } | {
647
+ target: string;
648
+ action: LogAction;
649
+ }))) => Promise<Log>;
650
+
518
651
  /**
519
652
  * Generate an express API route handler
520
653
  * @author Gabe Abrams
@@ -556,6 +689,7 @@ declare const genRouteHandler: (opts: {
556
689
  pageTitle?: string | undefined;
557
690
  status?: number | undefined;
558
691
  } | undefined) => void;
692
+ logServerEvent: LogFunction;
559
693
  }) => any;
560
694
  skipSessionCheck?: boolean | undefined;
561
695
  }) => (req: any, res: any, next: () => void) => Promise<undefined>;
@@ -594,10 +728,16 @@ declare type GetLaunchInfoFunction = (req: any) => {
594
728
  * Prepare dce-reactkit to run on the server
595
729
  * @author Gabe Abrams
596
730
  * @param opts object containing all arguments
731
+ * @param opts.app express app from inside of the postprocessor function that
732
+ * we will add routes to
597
733
  * @param opts.getLaunchInfo CACCL LTI's get launch info function
734
+ * @param [opts.logCollection] mongo collection from dce-mango to use for
735
+ * storing logs. If none is included, logs are written to the console
598
736
  */
599
737
  declare const initServer: (opts: {
738
+ app: any;
600
739
  getLaunchInfo: GetLaunchInfoFunction;
740
+ logCollection?: any;
601
741
  }) => void;
602
742
 
603
743
  /**
@@ -696,6 +836,12 @@ declare const onlyKeepLetters: (str: string) => string;
696
836
  */
697
837
  declare const parallelLimit: (taskFunctions: (() => Promise<any>)[], limit?: number | undefined) => Promise<any[]>;
698
838
 
839
+ /**
840
+ * Log a user action on the client (cannot be used on the server)
841
+ * @author Gabe Abrams
842
+ */
843
+ declare const logClientEvent: LogFunction;
844
+
699
845
  /**
700
846
  * List of error codes built into the react kit
701
847
  * @author Gabe Abrams
@@ -727,4 +873,4 @@ declare enum DayOfWeek {
727
873
  Sunday = "u"
728
874
  }
729
875
 
730
- export { AppWrapper, ButtonInputGroup, CheckboxButton, CopiableBox, DAY_IN_MS, DayOfWeek, Drawer, ErrorBox, ErrorWithCode, HOUR_IN_MS, ItemPicker, LoadingSpinner, MINUTE_IN_MS, Modal, ModalButtonType, ModalSize, ModalType, ParamType, PickableItem, PopFailureMark, PopPendingMark, PopSuccessMark, RadioButton, ReactKitErrorCode, SimpleDateChooser, TabBox, Variant, abbreviate, alert, avg, ceilToNumDecimals, confirm, floorToNumDecimals, forceNumIntoBounds, genRouteHandler, getHumanReadableDate, getOrdinal, getPartOfDay, getTimeInfoInET, handleError, handleSuccess, initServer, onlyKeepLetters, padDecimalZeros, padZerosLeft, parallelLimit, roundToNumDecimals, showFatalError, startMinWait, stringsToHumanReadableList, stubServerEndpoint, sum, visitServerEndpoint, waitMs };
876
+ export { AppWrapper, ButtonInputGroup, CheckboxButton, CopiableBox, DAY_IN_MS, DayOfWeek, Drawer, ErrorBox, ErrorWithCode, HOUR_IN_MS, ItemPicker, LoadingSpinner, Log, LogAction, LogSource, LogType, MINUTE_IN_MS, Modal, ModalButtonType, ModalSize, ModalType, ParamType, PickableItem, PopFailureMark, PopPendingMark, PopSuccessMark, RadioButton, ReactKitErrorCode, SimpleDateChooser, TabBox, Variant, abbreviate, alert, avg, ceilToNumDecimals, confirm, floorToNumDecimals, forceNumIntoBounds, genRouteHandler, getHumanReadableDate, getOrdinal, getPartOfDay, getTimeInfoInET, handleError, handleSuccess, initServer, logClientEvent, onlyKeepLetters, padDecimalZeros, padZerosLeft, parallelLimit, roundToNumDecimals, showFatalError, startMinWait, stringsToHumanReadableList, stubServerEndpoint, sum, visitServerEndpoint, waitMs };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dce-reactkit",
3
- "version": "3.1.20",
3
+ "version": "3.2.0-beta.2",
4
4
  "description": "Shared components for Harvard DCE apps",
5
5
  "main": "dist/cjs/index.js",
6
6
  "module": "dist/esm/index.js",
@@ -303,7 +303,7 @@ const AppWrapper: React.FC<Props> = (props: Props): React.ReactElement => {
303
303
  undefined
304
304
  | {
305
305
  title: string,
306
- text: string
306
+ text: string,
307
307
  }
308
308
  >(undefined);
309
309
  setAlertInfo = setAlertInfoInner;
@@ -0,0 +1,9 @@
1
+ import ROUTE_PATH_PREFIX from './ROUTE_PATH_PREFIX';
2
+
3
+ /**
4
+ * Path of the route for storing client-side logs
5
+ * @author Gabe Abrams
6
+ */
7
+ const LOG_ROUTE_PATH = `${ROUTE_PATH_PREFIX}/log`;
8
+
9
+ export default LOG_ROUTE_PATH;
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Path that all routes start with
3
+ * @author Gabe Abrams
4
+ */
5
+ const ROUTE_PATH_PREFIX = '/dce-reactkit';
6
+
7
+ export default ROUTE_PATH_PREFIX;
@@ -1,5 +1,8 @@
1
1
  // Import caccl functions
2
- import { cacclGetLaunchInfo } from '../server/initServer';
2
+ import {
3
+ cacclGetLaunchInfo,
4
+ internalGetLogCollection,
5
+ } from '../server/initServer';
3
6
 
4
7
  // Import shared types
5
8
  import ReactKitErrorCode from '../types/ReactKitErrorCode';
@@ -9,6 +12,17 @@ import ParamType from '../types/ParamType';
9
12
  import handleError from './handleError';
10
13
  import handleSuccess from './handleSuccess';
11
14
  import genErrorPage from '../html/genErrorPage';
15
+ import parseUserAgent from './parseUserAgent';
16
+ import getTimeInfoInET from './getTimeInfoInET';
17
+
18
+ // Import shared types
19
+ import LogFunction from '../types/LogFunction';
20
+ import Log from '../types/Log';
21
+ import LogType from '../types/LogType';
22
+ import LogSource from '../types/LogSource';
23
+ import LogTypeSpecificInfo from '../types/Log/LogTypeSpecificInfo';
24
+ import LogMainInfo from '../types/Log/LogMainInfo';
25
+ import LogSourceSpecificInfo from '../types/Log/LogSourceSpecificInfo';
12
26
 
13
27
  /**
14
28
  * Generate an express API route handler
@@ -55,6 +69,7 @@ const genRouteHandler = (
55
69
  status?: number,
56
70
  },
57
71
  ) => void,
72
+ logServerEvent: LogFunction,
58
73
  },
59
74
  ) => any,
60
75
  skipSessionCheck?: boolean,
@@ -439,6 +454,126 @@ const genRouteHandler = (
439
454
  );
440
455
  }
441
456
 
457
+ /*----------------------------------------*/
458
+ /* Log Handler */
459
+ /*----------------------------------------*/
460
+
461
+ // Create a log handler function
462
+
463
+ /**
464
+ * Log an event on the server
465
+ * @author Gabe Abrams
466
+ */
467
+ const logServerEvent: LogFunction = async (opts) => {
468
+ // NOTE: internally, we slip through an opts.overrideAsClientEvent boolean
469
+ // that indicates that this is actually a client event, but we don't
470
+ // include that in the LogFunction type because this is internal and
471
+ // hidden from users
472
+
473
+ // Parse user agent
474
+ const {
475
+ browser,
476
+ device,
477
+ } = parseUserAgent(req.headers['user-agent']);
478
+
479
+ // Get time info in ET
480
+ const {
481
+ timestamp,
482
+ year,
483
+ month,
484
+ day,
485
+ hour,
486
+ minute,
487
+ } = getTimeInfoInET();
488
+
489
+ // Main log info
490
+ const mainLogInfo: LogMainInfo = {
491
+ id: `${launchInfo.userId}-${Date.now()}-${Math.floor(Math.random() * 100000)}-${Math.floor(Math.random() * 100000)}`,
492
+ userFirstName: launchInfo.userFirstName,
493
+ userLastName: launchInfo.userLastName,
494
+ userEmail: launchInfo.userEmail,
495
+ userId: launchInfo.userId,
496
+ isLearner: !!launchInfo.isLearner,
497
+ isAdmin: !!launchInfo.isAdmin,
498
+ isTTM: !!launchInfo.isTTM,
499
+ courseId: launchInfo.courseId,
500
+ courseName: launchInfo.courseName,
501
+ browser,
502
+ device,
503
+ year,
504
+ month,
505
+ day,
506
+ hour,
507
+ minute,
508
+ timestamp,
509
+ category: (
510
+ typeof opts.category === 'string'
511
+ ? opts.category
512
+ : opts.category.name
513
+ ),
514
+ subcategory: (
515
+ typeof opts.category === 'string'
516
+ ? opts.subcategory
517
+ : ((opts.subcategory as any) ?? { name: 'none' }).name
518
+ ),
519
+ tags: opts.tags ?? [],
520
+ metadata: opts.metadata ?? {},
521
+ };
522
+
523
+ // Type-specific info
524
+ const typeSpecificInfo: LogTypeSpecificInfo = (
525
+ ('error' in opts && opts.error)
526
+ ? {
527
+ type: LogType.Error,
528
+ errorMessage: opts.error.message ?? 'Unknown message',
529
+ errorCode: opts.error.code ?? ReactKitErrorCode.NoCode,
530
+ errorStack: opts.error.stack ?? 'No stack',
531
+ }
532
+ : {
533
+ type: LogType.Action,
534
+ target: (opts as any).target ?? 'Unknown',
535
+ action: (opts as any).action ?? 'Unknown'
536
+ }
537
+ );
538
+
539
+ // Source-specific info
540
+ const sourceSpecificInfo: LogSourceSpecificInfo = (
541
+ (opts as any).overrideAsClientEvent
542
+ ? {
543
+ source: LogSource.Client,
544
+ }
545
+ : {
546
+ source: LogSource.Server,
547
+ routePath: req.path,
548
+ routeTemplate: req.route.path,
549
+ }
550
+ );
551
+
552
+ // Build log event
553
+ const log: Log = {
554
+ ...mainLogInfo,
555
+ ...typeSpecificInfo,
556
+ ...sourceSpecificInfo,
557
+ };
558
+
559
+ // Either print to console or save to db
560
+ const logCollection = internalGetLogCollection();
561
+ if (logCollection) {
562
+ // Store to the log collection
563
+ await logCollection.insert(log);
564
+ } else {
565
+ // Print to console
566
+ if (log.type === LogType.Error) {
567
+ console.error('dce-reactkit error log:', log);
568
+ } else {
569
+ console.log('dce-reactkit action log:', log);
570
+ }
571
+ }
572
+
573
+ // Return log entry
574
+ return log;
575
+ };
576
+
442
577
  /*------------------------------------------------------------------------*/
443
578
  /* Call handler */
444
579
  /*------------------------------------------------------------------------*/
@@ -504,6 +639,7 @@ const genRouteHandler = (
504
639
  },
505
640
  redirect,
506
641
  renderErrorPage,
642
+ logServerEvent,
507
643
  });
508
644
 
509
645
  // Send results to client (only if next wasn't called)
@@ -0,0 +1,58 @@
1
+ // Import shared types
2
+ import LOG_ROUTE_PATH from '../constants/LOG_ROUTE_PATH';
3
+ import LogFunction from '../types/LogFunction';
4
+
5
+ // Import shared functions
6
+ import visitServerEndpoint from './visitServerEndpoint';
7
+
8
+ /**
9
+ * Log a user action on the client (cannot be used on the server)
10
+ * @author Gabe Abrams
11
+ */
12
+ const logClientEvent: LogFunction = async (opts) => {
13
+ return visitServerEndpoint({
14
+ path: LOG_ROUTE_PATH,
15
+ method: 'POST',
16
+ params: {
17
+ category: (
18
+ typeof opts.category === 'string'
19
+ ? opts.category
20
+ : opts.category.name
21
+ ),
22
+ subcategory: (
23
+ typeof opts.category === 'string'
24
+ ? opts.subcategory
25
+ : ((opts.subcategory as any) ?? { name: 'none' }).name
26
+ ),
27
+ tags: JSON.stringify(opts.tags),
28
+ metadata: JSON.stringify(opts.metadata ?? {}),
29
+ errorMessage: (
30
+ (opts as any).error
31
+ ? (opts as any).error.message
32
+ : undefined
33
+ ),
34
+ errorCode: (
35
+ (opts as any).error
36
+ ? (opts as any).error.code
37
+ : undefined
38
+ ),
39
+ errorStack: (
40
+ (opts as any).error
41
+ ? (opts as any).error.stack
42
+ : undefined
43
+ ),
44
+ target: (
45
+ (opts as any).target
46
+ ? (opts as any).target
47
+ : undefined
48
+ ),
49
+ action: (
50
+ (opts as any).action
51
+ ? (opts as any).action
52
+ : undefined
53
+ ),
54
+ },
55
+ });
56
+ };
57
+
58
+ export default logClientEvent;
@@ -0,0 +1,108 @@
1
+ /**
2
+ * Perform a rudimentary parsing of the user's browser agent string
3
+ * @author Gabe Abrams
4
+ * @param userAgent the user's browser agent
5
+ * @returns user info
6
+ */
7
+ const parseUserAgent = (userAgent: string) => {
8
+ /* ------------- Browser ------------ */
9
+
10
+ let browser: { name: string, version: string } = {
11
+ name: 'Unknown',
12
+ version: 'Unknown',
13
+ };
14
+
15
+ // Parse user agent
16
+ let verOffset: number;
17
+ let nameOffset: number;
18
+ if ((verOffset = userAgent.indexOf('Opera')) !== -1) {
19
+ // In Opera, the true version is after 'Opera' or after 'Version'
20
+ browser = {
21
+ name: 'Opera',
22
+ version: userAgent.substring(verOffset + 6),
23
+ };
24
+ if ((verOffset = userAgent.indexOf('Version')) !== -1) {
25
+ browser.version = userAgent.substring(verOffset + 8);
26
+ }
27
+ } else if ((verOffset = userAgent.indexOf('MSIE')) !== -1) {
28
+ // In MSIE, the true version is after 'MSIE' in userAgent
29
+ browser = {
30
+ name: 'Internet Explorer',
31
+ version: userAgent.substring(verOffset + 5),
32
+ };
33
+ } else if ((verOffset = userAgent.indexOf('Chrome')) !== -1) {
34
+ // In Chrome, the true version is after 'Chrome'
35
+ browser = {
36
+ name: 'Chrome',
37
+ version: userAgent.substring(verOffset + 7),
38
+ };
39
+ } else if ((verOffset = userAgent.indexOf('Safari')) !== -1) {
40
+ // In Safari, the true version is after 'Safari' or after 'Version'
41
+ browser = {
42
+ name: 'Safari',
43
+ version: userAgent.substring(verOffset + 7),
44
+ };
45
+ if ((verOffset = userAgent.indexOf('Version')) !== -1) {
46
+ browser.version = userAgent.substring(verOffset + 8);
47
+ }
48
+ } else if ((verOffset = userAgent.indexOf('Firefox')) != -1) {
49
+ // In Firefox, the true version is after 'Firefox'
50
+ browser = {
51
+ name: 'Firefox',
52
+ version: userAgent.substring(verOffset + 8),
53
+ };
54
+ } else if (
55
+ (nameOffset = userAgent.lastIndexOf(' ') + 1)
56
+ < (verOffset = userAgent.lastIndexOf('/'))
57
+ ) {
58
+ browser = {
59
+ name: userAgent.substring(nameOffset, verOffset),
60
+ version: userAgent.substring(verOffset + 1),
61
+ };
62
+ }
63
+
64
+ // Postprocess version
65
+ // trim the fullVersion string at semicolon/space if present
66
+ let ix: number;
67
+ if ((ix = browser.version.indexOf(';')) !== -1) {
68
+ browser.version = browser.version.substring(0, ix);
69
+ }
70
+ if ((ix = browser.version.indexOf(' ')) !== -1) {
71
+ browser.version = browser.version.substring(0, ix);
72
+ }
73
+
74
+ /* ------------- Device ------------- */
75
+
76
+ // Detect os
77
+ let os = 'Unknown';
78
+ if (userAgent.includes('Linux')) {
79
+ os = 'Linux';
80
+ } else if (userAgent.includes('like Mac')) {
81
+ os = 'iOS';
82
+ } else if (userAgent.includes('Mac')) {
83
+ os = 'Mac';
84
+ } else if (userAgent.includes('Android')) {
85
+ os = 'Android';
86
+ } else if (userAgent.includes('Win')) {
87
+ os = 'Win';
88
+ }
89
+
90
+ // Check if mobile
91
+ const isMobile = !!userAgent.match(/(iPhone|iPod|iPad|Android|BlackBerry)/);
92
+
93
+ // Device
94
+ const device = {
95
+ isMobile,
96
+ os,
97
+ };
98
+
99
+ /* ------------- Finish ------------- */
100
+
101
+ // Return info
102
+ return {
103
+ browser,
104
+ device,
105
+ };
106
+ };
107
+
108
+ export default parseUserAgent;
package/src/index.ts CHANGED
@@ -48,6 +48,7 @@ import getPartOfDay from './helpers/getPartOfDay';
48
48
  import stringsToHumanReadableList from './helpers/stringsToHumanReadableList';
49
49
  import onlyKeepLetters from './helpers/onlyKeepLetters';
50
50
  import parallelLimit from './helpers/parallelLimit';
51
+ import logClientEvent from './helpers/logClientEvent';
51
52
 
52
53
  // Import types
53
54
  import ModalButtonType from './types/ModalButtonType';
@@ -57,6 +58,10 @@ import ReactKitErrorCode from './types/ReactKitErrorCode';
57
58
  import Variant from './types/Variant';
58
59
  import ParamType from './types/ParamType';
59
60
  import DayOfWeek from './types/DayOfWeek';
61
+ import Log from './types/Log';
62
+ import LogType from './types/LogType';
63
+ import LogSource from './types/LogSource';
64
+ import LogAction from './types/LogAction';
60
65
 
61
66
  // Component-specific-types
62
67
  import PickableItem from './components/ItemPicker/types/PickableItem';
@@ -111,6 +116,7 @@ export {
111
116
  parallelLimit,
112
117
  // Client helpers
113
118
  visitServerEndpoint,
119
+ logClientEvent,
114
120
  // Server helpers
115
121
  initServer,
116
122
  genRouteHandler,
@@ -123,6 +129,10 @@ export {
123
129
  ReactKitErrorCode,
124
130
  Variant,
125
131
  DayOfWeek,
132
+ Log,
133
+ LogType,
134
+ LogSource,
135
+ LogAction,
126
136
  // Component-specific-types
127
137
  PickableItem,
128
138
  // Server types