dce-reactkit 3.1.20 → 3.2.0-beta.1
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.
- package/dist/cjs/index.js +363 -19
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/types/constants/LOG_ROUTE_PATH.d.ts +6 -0
- package/dist/cjs/types/constants/ROUTE_PATH_PREFIX.d.ts +6 -0
- package/dist/cjs/types/helpers/genRouteHandler.d.ts +2 -0
- package/dist/cjs/types/helpers/logClientEvent.d.ts +7 -0
- package/dist/cjs/types/helpers/parseUserAgent.d.ts +17 -0
- package/dist/cjs/types/index.d.ts +5 -1
- package/dist/cjs/types/server/initServer.d.ts +13 -0
- package/dist/cjs/types/types/Log/LogMainInfo.d.ts +37 -0
- package/dist/cjs/types/types/Log/LogSourceSpecificInfo.d.ts +13 -0
- package/dist/cjs/types/types/Log/LogTypeSpecificInfo.d.ts +17 -0
- package/dist/cjs/types/types/Log/index.d.ts +10 -0
- package/dist/cjs/types/types/LogAction.d.ts +22 -0
- package/dist/cjs/types/types/LogFunction.d.ts +20 -0
- package/dist/cjs/types/types/LogSource.d.ts +9 -0
- package/dist/cjs/types/types/LogType.d.ts +9 -0
- package/dist/esm/index.js +361 -20
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/types/constants/LOG_ROUTE_PATH.d.ts +6 -0
- package/dist/esm/types/constants/ROUTE_PATH_PREFIX.d.ts +6 -0
- package/dist/esm/types/helpers/genRouteHandler.d.ts +2 -0
- package/dist/esm/types/helpers/logClientEvent.d.ts +7 -0
- package/dist/esm/types/helpers/parseUserAgent.d.ts +17 -0
- package/dist/esm/types/index.d.ts +5 -1
- package/dist/esm/types/server/initServer.d.ts +13 -0
- package/dist/esm/types/types/Log/LogMainInfo.d.ts +37 -0
- package/dist/esm/types/types/Log/LogSourceSpecificInfo.d.ts +13 -0
- package/dist/esm/types/types/Log/LogTypeSpecificInfo.d.ts +17 -0
- package/dist/esm/types/types/Log/index.d.ts +10 -0
- package/dist/esm/types/types/LogAction.d.ts +22 -0
- package/dist/esm/types/types/LogFunction.d.ts +20 -0
- package/dist/esm/types/types/LogSource.d.ts +9 -0
- package/dist/esm/types/types/LogType.d.ts +9 -0
- package/dist/index.d.ts +137 -1
- package/package.json +1 -1
- package/src/components/AppWrapper.tsx +1 -1
- package/src/constants/LOG_ROUTE_PATH.ts +9 -0
- package/src/constants/ROUTE_PATH_PREFIX.ts +7 -0
- package/src/helpers/genRouteHandler.ts +129 -1
- package/src/helpers/logClientEvent.tsx +50 -0
- package/src/helpers/parseUserAgent.ts +108 -0
- package/src/index.ts +8 -0
- package/src/server/initServer.ts +90 -0
- package/src/types/Log/LogMainInfo.ts +64 -0
- package/src/types/Log/LogSourceSpecificInfo.ts +25 -0
- package/src/types/Log/LogTypeSpecificInfo.ts +33 -0
- package/src/types/Log/index.ts +17 -0
- package/src/types/LogAction.ts +38 -0
- package/src/types/LogFunction.ts +41 -0
- package/src/types/LogSource.ts +12 -0
- package/src/types/LogType.ts +12 -0
package/dist/index.d.ts
CHANGED
|
@@ -515,6 +515,135 @@ 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
|
+
subcategory?: string;
|
|
636
|
+
tags?: string[];
|
|
637
|
+
metadata?: {
|
|
638
|
+
[k: string]: any;
|
|
639
|
+
};
|
|
640
|
+
} & ({
|
|
641
|
+
error: any;
|
|
642
|
+
} | {
|
|
643
|
+
target: string;
|
|
644
|
+
action: LogAction;
|
|
645
|
+
}))) => Promise<Log>;
|
|
646
|
+
|
|
518
647
|
/**
|
|
519
648
|
* Generate an express API route handler
|
|
520
649
|
* @author Gabe Abrams
|
|
@@ -556,6 +685,7 @@ declare const genRouteHandler: (opts: {
|
|
|
556
685
|
pageTitle?: string | undefined;
|
|
557
686
|
status?: number | undefined;
|
|
558
687
|
} | undefined) => void;
|
|
688
|
+
logServerEvent: LogFunction;
|
|
559
689
|
}) => any;
|
|
560
690
|
skipSessionCheck?: boolean | undefined;
|
|
561
691
|
}) => (req: any, res: any, next: () => void) => Promise<undefined>;
|
|
@@ -594,10 +724,16 @@ declare type GetLaunchInfoFunction = (req: any) => {
|
|
|
594
724
|
* Prepare dce-reactkit to run on the server
|
|
595
725
|
* @author Gabe Abrams
|
|
596
726
|
* @param opts object containing all arguments
|
|
727
|
+
* @param opts.app express app from inside of the postprocessor function that
|
|
728
|
+
* we will add routes to
|
|
597
729
|
* @param opts.getLaunchInfo CACCL LTI's get launch info function
|
|
730
|
+
* @param [opts.logCollection] mongo collection from dce-mango to use for
|
|
731
|
+
* storing logs. If none is included, logs are written to the console
|
|
598
732
|
*/
|
|
599
733
|
declare const initServer: (opts: {
|
|
734
|
+
app: any;
|
|
600
735
|
getLaunchInfo: GetLaunchInfoFunction;
|
|
736
|
+
logCollection?: any;
|
|
601
737
|
}) => void;
|
|
602
738
|
|
|
603
739
|
/**
|
|
@@ -727,4 +863,4 @@ declare enum DayOfWeek {
|
|
|
727
863
|
Sunday = "u"
|
|
728
864
|
}
|
|
729
865
|
|
|
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 };
|
|
866
|
+
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, onlyKeepLetters, padDecimalZeros, padZerosLeft, parallelLimit, roundToNumDecimals, showFatalError, startMinWait, stringsToHumanReadableList, stubServerEndpoint, sum, visitServerEndpoint, waitMs };
|
package/package.json
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
// Import caccl functions
|
|
2
|
-
import {
|
|
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,118 @@ 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: opts.category,
|
|
510
|
+
subcategory: opts.subcategory ?? 'none',
|
|
511
|
+
tags: opts.tags ?? [],
|
|
512
|
+
metadata: opts.metadata ?? {},
|
|
513
|
+
};
|
|
514
|
+
|
|
515
|
+
// Type-specific info
|
|
516
|
+
const typeSpecificInfo: LogTypeSpecificInfo = (
|
|
517
|
+
('error' in opts && opts.error)
|
|
518
|
+
? {
|
|
519
|
+
type: LogType.Error,
|
|
520
|
+
errorMessage: opts.error.message ?? 'Unknown message',
|
|
521
|
+
errorCode: opts.error.code ?? ReactKitErrorCode.NoCode,
|
|
522
|
+
errorStack: opts.error.stack ?? 'No stack',
|
|
523
|
+
}
|
|
524
|
+
: {
|
|
525
|
+
type: LogType.Action,
|
|
526
|
+
target: (opts as any).target ?? 'Unknown',
|
|
527
|
+
action: (opts as any).action ?? 'Unknown'
|
|
528
|
+
}
|
|
529
|
+
);
|
|
530
|
+
|
|
531
|
+
// Source-specific info
|
|
532
|
+
const sourceSpecificInfo: LogSourceSpecificInfo = (
|
|
533
|
+
(opts as any).overrideAsClientEvent
|
|
534
|
+
? {
|
|
535
|
+
source: LogSource.Client,
|
|
536
|
+
}
|
|
537
|
+
: {
|
|
538
|
+
source: LogSource.Server,
|
|
539
|
+
routePath: req.path,
|
|
540
|
+
routeTemplate: req.route.path,
|
|
541
|
+
}
|
|
542
|
+
);
|
|
543
|
+
|
|
544
|
+
// Build log event
|
|
545
|
+
const log: Log = {
|
|
546
|
+
...mainLogInfo,
|
|
547
|
+
...typeSpecificInfo,
|
|
548
|
+
...sourceSpecificInfo,
|
|
549
|
+
};
|
|
550
|
+
|
|
551
|
+
// Either print to console or save to db
|
|
552
|
+
const logCollection = internalGetLogCollection();
|
|
553
|
+
if (logCollection) {
|
|
554
|
+
// Store to the log collection
|
|
555
|
+
await logCollection.insert(log);
|
|
556
|
+
} else {
|
|
557
|
+
// Print to console
|
|
558
|
+
if (log.type === LogType.Error) {
|
|
559
|
+
console.error('dce-reactkit error log:', log);
|
|
560
|
+
} else {
|
|
561
|
+
console.log('dce-reactkit action log:', log);
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
// Return log entry
|
|
566
|
+
return log;
|
|
567
|
+
};
|
|
568
|
+
|
|
442
569
|
/*------------------------------------------------------------------------*/
|
|
443
570
|
/* Call handler */
|
|
444
571
|
/*------------------------------------------------------------------------*/
|
|
@@ -504,6 +631,7 @@ const genRouteHandler = (
|
|
|
504
631
|
},
|
|
505
632
|
redirect,
|
|
506
633
|
renderErrorPage,
|
|
634
|
+
logServerEvent,
|
|
507
635
|
});
|
|
508
636
|
|
|
509
637
|
// Send results to client (only if next wasn't called)
|
|
@@ -0,0 +1,50 @@
|
|
|
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: opts.category,
|
|
18
|
+
subcategory: opts.subcategory,
|
|
19
|
+
tags: JSON.stringify(opts.tags),
|
|
20
|
+
metadata: JSON.stringify(opts.metadata ?? {}),
|
|
21
|
+
errorMessage: (
|
|
22
|
+
(opts as any).error
|
|
23
|
+
? (opts as any).error.message
|
|
24
|
+
: undefined
|
|
25
|
+
),
|
|
26
|
+
errorCode: (
|
|
27
|
+
(opts as any).error
|
|
28
|
+
? (opts as any).error.code
|
|
29
|
+
: undefined
|
|
30
|
+
),
|
|
31
|
+
errorStack: (
|
|
32
|
+
(opts as any).error
|
|
33
|
+
? (opts as any).error.stack
|
|
34
|
+
: undefined
|
|
35
|
+
),
|
|
36
|
+
target: (
|
|
37
|
+
(opts as any).target
|
|
38
|
+
? (opts as any).target
|
|
39
|
+
: undefined
|
|
40
|
+
),
|
|
41
|
+
action: (
|
|
42
|
+
(opts as any).action
|
|
43
|
+
? (opts as any).action
|
|
44
|
+
: undefined
|
|
45
|
+
),
|
|
46
|
+
},
|
|
47
|
+
});
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
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
|
@@ -57,6 +57,10 @@ import ReactKitErrorCode from './types/ReactKitErrorCode';
|
|
|
57
57
|
import Variant from './types/Variant';
|
|
58
58
|
import ParamType from './types/ParamType';
|
|
59
59
|
import DayOfWeek from './types/DayOfWeek';
|
|
60
|
+
import Log from './types/Log';
|
|
61
|
+
import LogType from './types/LogType';
|
|
62
|
+
import LogSource from './types/LogSource';
|
|
63
|
+
import LogAction from './types/LogAction';
|
|
60
64
|
|
|
61
65
|
// Component-specific-types
|
|
62
66
|
import PickableItem from './components/ItemPicker/types/PickableItem';
|
|
@@ -123,6 +127,10 @@ export {
|
|
|
123
127
|
ReactKitErrorCode,
|
|
124
128
|
Variant,
|
|
125
129
|
DayOfWeek,
|
|
130
|
+
Log,
|
|
131
|
+
LogType,
|
|
132
|
+
LogSource,
|
|
133
|
+
LogAction,
|
|
126
134
|
// Component-specific-types
|
|
127
135
|
PickableItem,
|
|
128
136
|
// Server types
|
package/src/server/initServer.ts
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
// Import custom error
|
|
2
|
+
import LOG_ROUTE_PATH from '../constants/LOG_ROUTE_PATH';
|
|
2
3
|
import ErrorWithCode from '../errors/ErrorWithCode';
|
|
4
|
+
|
|
5
|
+
// Import shared helpers
|
|
6
|
+
import genRouteHandler from '../helpers/genRouteHandler';
|
|
7
|
+
import ParamType from '../types/ParamType';
|
|
8
|
+
|
|
9
|
+
// Import shared types
|
|
3
10
|
import ReactKitErrorCode from '../types/ReactKitErrorCode';
|
|
4
11
|
|
|
5
12
|
// Types
|
|
@@ -11,6 +18,9 @@ type GetLaunchInfoFunction = (req: any) => {
|
|
|
11
18
|
// Stored copy of caccl functions
|
|
12
19
|
let _cacclGetLaunchInfo: GetLaunchInfoFunction;
|
|
13
20
|
|
|
21
|
+
// Stored copy of dce-mango log collection
|
|
22
|
+
let _logCollection: any;
|
|
23
|
+
|
|
14
24
|
/*------------------------------------------------------------------------*/
|
|
15
25
|
/* Helpers */
|
|
16
26
|
/*------------------------------------------------------------------------*/
|
|
@@ -32,6 +42,16 @@ export const cacclGetLaunchInfo: GetLaunchInfoFunction = (req: any) => {
|
|
|
32
42
|
return _cacclGetLaunchInfo(req);
|
|
33
43
|
};
|
|
34
44
|
|
|
45
|
+
/**
|
|
46
|
+
* Get log collection
|
|
47
|
+
* @author Gabe Abrams
|
|
48
|
+
* @returns log collection if one was included during launch or null if we don't
|
|
49
|
+
* have a log collection (yet)
|
|
50
|
+
*/
|
|
51
|
+
export const internalGetLogCollection = () => {
|
|
52
|
+
return _logCollection ?? null;
|
|
53
|
+
};
|
|
54
|
+
|
|
35
55
|
/*------------------------------------------------------------------------*/
|
|
36
56
|
/* Main */
|
|
37
57
|
/*------------------------------------------------------------------------*/
|
|
@@ -40,14 +60,84 @@ export const cacclGetLaunchInfo: GetLaunchInfoFunction = (req: any) => {
|
|
|
40
60
|
* Prepare dce-reactkit to run on the server
|
|
41
61
|
* @author Gabe Abrams
|
|
42
62
|
* @param opts object containing all arguments
|
|
63
|
+
* @param opts.app express app from inside of the postprocessor function that
|
|
64
|
+
* we will add routes to
|
|
43
65
|
* @param opts.getLaunchInfo CACCL LTI's get launch info function
|
|
66
|
+
* @param [opts.logCollection] mongo collection from dce-mango to use for
|
|
67
|
+
* storing logs. If none is included, logs are written to the console
|
|
44
68
|
*/
|
|
45
69
|
const initServer = (
|
|
46
70
|
opts: {
|
|
71
|
+
app: any,
|
|
47
72
|
getLaunchInfo: GetLaunchInfoFunction,
|
|
73
|
+
logCollection?: any,
|
|
48
74
|
},
|
|
49
75
|
) => {
|
|
50
76
|
_cacclGetLaunchInfo = opts.getLaunchInfo;
|
|
77
|
+
_logCollection = opts.logCollection;
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Log an event
|
|
81
|
+
* @author Gabe Abrams
|
|
82
|
+
* @param {string} category Category of the event (each app determines how to
|
|
83
|
+
* categorize its events)
|
|
84
|
+
* @param {string} subcategory Subcategory of the event (each app determines
|
|
85
|
+
* how to categorize its events)
|
|
86
|
+
* @param {string} tags stringified list of tags that apply to this action
|
|
87
|
+
* (each app determines tag usage)
|
|
88
|
+
* @param {string} metadata stringified object containing optional custom metadata
|
|
89
|
+
* @param {string} [errorMessage] error message if type is an error
|
|
90
|
+
* @param {string} [errorCode] error code if type is an error
|
|
91
|
+
* @param {string} [errorStack] error stack if type is an error
|
|
92
|
+
* @param {string} [target] Target of the action (each app determines the list
|
|
93
|
+
* of targets) These are usually buttons, panels, elements, etc.
|
|
94
|
+
* @param {LogAction} [action] the type of action performed on the target
|
|
95
|
+
* @returns {Log}
|
|
96
|
+
*/
|
|
97
|
+
opts.app.post(
|
|
98
|
+
LOG_ROUTE_PATH,
|
|
99
|
+
genRouteHandler({
|
|
100
|
+
paramTypes: {
|
|
101
|
+
category: ParamType.String,
|
|
102
|
+
subcategory: ParamType.String,
|
|
103
|
+
tags: ParamType.JSON,
|
|
104
|
+
metadata: ParamType.JSON,
|
|
105
|
+
errorMessage: ParamType.StringOptional,
|
|
106
|
+
errorCode: ParamType.StringOptional,
|
|
107
|
+
errorStack: ParamType.StringOptional,
|
|
108
|
+
target: ParamType.StringOptional,
|
|
109
|
+
action: ParamType.StringOptional,
|
|
110
|
+
},
|
|
111
|
+
handler: ({ params, logServerEvent }) => {
|
|
112
|
+
const log = logServerEvent(
|
|
113
|
+
(params.errorMessage || params.errorCode || params.errorStack)
|
|
114
|
+
// Error
|
|
115
|
+
? {
|
|
116
|
+
category: params.category,
|
|
117
|
+
subcategory: params.subcategory,
|
|
118
|
+
tags: params.tags,
|
|
119
|
+
metadata: params.metadata,
|
|
120
|
+
error: {
|
|
121
|
+
message: params.errorMessage,
|
|
122
|
+
code: params.errorCode,
|
|
123
|
+
stack: params.errorStack,
|
|
124
|
+
},
|
|
125
|
+
}
|
|
126
|
+
// Action
|
|
127
|
+
: {
|
|
128
|
+
category: params.category,
|
|
129
|
+
subcategory: params.subcategory,
|
|
130
|
+
tags: params.tags,
|
|
131
|
+
metadata: params.metadata,
|
|
132
|
+
target: params.target,
|
|
133
|
+
action: params.action,
|
|
134
|
+
}
|
|
135
|
+
);
|
|
136
|
+
|
|
137
|
+
return log;
|
|
138
|
+
},
|
|
139
|
+
}),
|
|
140
|
+
);
|
|
51
141
|
};
|
|
52
142
|
|
|
53
143
|
export default initServer;
|