scorm-again 1.7.1 → 2.1.0

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 (124) hide show
  1. package/.babelrc +18 -7
  2. package/.github/dependabot.yml +5 -0
  3. package/.github/workflows/main.yml +79 -0
  4. package/.github/workflows/stale.yml +14 -0
  5. package/.jsdoc.json +4 -5
  6. package/.mocharc.json +8 -0
  7. package/.run/{Mocha Unit Tests.run.xml → Mocha Unit Tests (watch).run.xml } +6 -3
  8. package/.run/Template Mocha.run.xml +17 -0
  9. package/CONTRIBUTING.md +1 -1
  10. package/README.md +183 -71
  11. package/dist/aicc.js +3822 -7030
  12. package/dist/aicc.js.map +1 -1
  13. package/dist/aicc.min.js +2 -40
  14. package/dist/aicc.min.js.map +1 -0
  15. package/dist/scorm-again.js +5965 -10498
  16. package/dist/scorm-again.js.map +1 -1
  17. package/dist/scorm-again.min.js +2 -52
  18. package/dist/scorm-again.min.js.map +1 -0
  19. package/dist/scorm12.js +3028 -5373
  20. package/dist/scorm12.js.map +1 -1
  21. package/dist/scorm12.min.js +2 -34
  22. package/dist/scorm12.min.js.map +1 -0
  23. package/dist/scorm2004.js +4054 -6693
  24. package/dist/scorm2004.js.map +1 -1
  25. package/dist/scorm2004.min.js +2 -40
  26. package/dist/scorm2004.min.js.map +1 -0
  27. package/eslint.config.js +21 -0
  28. package/package.json +76 -34
  29. package/results.json +34254 -0
  30. package/src/AICC.ts +72 -0
  31. package/src/BaseAPI.ts +1300 -0
  32. package/src/Scorm12API.ts +387 -0
  33. package/src/Scorm2004API.ts +688 -0
  34. package/src/cmi/aicc/attempts.ts +94 -0
  35. package/src/cmi/aicc/cmi.ts +100 -0
  36. package/src/cmi/aicc/core.ts +360 -0
  37. package/src/cmi/aicc/evaluation.ts +157 -0
  38. package/src/cmi/aicc/paths.ts +180 -0
  39. package/src/cmi/aicc/student_data.ts +86 -0
  40. package/src/cmi/aicc/student_demographics.ts +367 -0
  41. package/src/cmi/aicc/student_preferences.ts +176 -0
  42. package/src/cmi/aicc/tries.ts +116 -0
  43. package/src/cmi/aicc/validation.ts +25 -0
  44. package/src/cmi/common/array.ts +77 -0
  45. package/src/cmi/common/base_cmi.ts +46 -0
  46. package/src/cmi/common/score.ts +203 -0
  47. package/src/cmi/common/validation.ts +60 -0
  48. package/src/cmi/scorm12/cmi.ts +224 -0
  49. package/src/cmi/scorm12/interactions.ts +368 -0
  50. package/src/cmi/scorm12/nav.ts +54 -0
  51. package/src/cmi/scorm12/objectives.ts +112 -0
  52. package/src/cmi/scorm12/student_data.ts +130 -0
  53. package/src/cmi/scorm12/student_preference.ts +158 -0
  54. package/src/cmi/scorm12/validation.ts +48 -0
  55. package/src/cmi/scorm2004/adl.ts +272 -0
  56. package/src/cmi/scorm2004/cmi.ts +599 -0
  57. package/src/cmi/scorm2004/comments.ts +163 -0
  58. package/src/cmi/scorm2004/interactions.ts +466 -0
  59. package/src/cmi/scorm2004/learner_preference.ts +152 -0
  60. package/src/cmi/scorm2004/objectives.ts +212 -0
  61. package/src/cmi/scorm2004/score.ts +78 -0
  62. package/src/cmi/scorm2004/validation.ts +42 -0
  63. package/src/constants/api_constants.ts +318 -0
  64. package/src/constants/default_settings.ts +81 -0
  65. package/src/constants/enums.ts +5 -0
  66. package/src/constants/error_codes.ts +88 -0
  67. package/src/constants/language_constants.ts +394 -0
  68. package/src/constants/regex.ts +97 -0
  69. package/src/constants/{response_constants.js → response_constants.ts} +69 -62
  70. package/src/exceptions.ts +154 -0
  71. package/src/exports/aicc.js +1 -1
  72. package/src/exports/scorm-again.js +3 -3
  73. package/src/exports/scorm12.js +1 -1
  74. package/src/exports/scorm2004.js +1 -1
  75. package/src/helpers/scheduled_commit.ts +42 -0
  76. package/src/interfaces/IBaseAPI.ts +35 -0
  77. package/src/types/api_types.ts +32 -0
  78. package/src/utilities/debounce.ts +31 -0
  79. package/src/utilities.ts +338 -0
  80. package/tea.yaml +6 -0
  81. package/test/{AICC.spec.js → AICC.spec.ts} +79 -71
  82. package/test/Scorm12API.spec.ts +833 -0
  83. package/test/Scorm2004API.spec.ts +1298 -0
  84. package/test/api_helpers.ts +176 -0
  85. package/test/cmi/aicc_cmi.spec.ts +845 -0
  86. package/test/cmi/{scorm12_cmi.spec.js → scorm12_cmi.spec.ts} +253 -271
  87. package/test/cmi/scorm2004_cmi.spec.ts +1031 -0
  88. package/test/cmi_helpers.ts +207 -0
  89. package/test/exceptions.spec.ts +79 -0
  90. package/test/field_values.ts +202 -0
  91. package/test/types/api_types.spec.ts +126 -0
  92. package/test/utilities/debounce.spec.ts +56 -0
  93. package/test/utilities.spec.ts +322 -0
  94. package/tsconfig.json +18 -0
  95. package/webpack.config.js +65 -0
  96. package/.circleci/config.yml +0 -99
  97. package/.codeclimate.yml +0 -7
  98. package/.eslintrc.js +0 -36
  99. package/src/.flowconfig +0 -11
  100. package/src/AICC.js +0 -68
  101. package/src/BaseAPI.js +0 -1275
  102. package/src/Scorm12API.js +0 -308
  103. package/src/Scorm2004API.js +0 -572
  104. package/src/cmi/aicc_cmi.js +0 -1141
  105. package/src/cmi/common.js +0 -328
  106. package/src/cmi/scorm12_cmi.js +0 -1312
  107. package/src/cmi/scorm2004_cmi.js +0 -1692
  108. package/src/constants/api_constants.js +0 -218
  109. package/src/constants/error_codes.js +0 -87
  110. package/src/constants/language_constants.js +0 -76
  111. package/src/constants/regex.js +0 -84
  112. package/src/exceptions.js +0 -104
  113. package/src/utilities.js +0 -242
  114. package/test/Scorm12API.spec.js +0 -528
  115. package/test/Scorm2004API.spec.js +0 -775
  116. package/test/abstract_classes.spec.js +0 -17
  117. package/test/api_helpers.js +0 -128
  118. package/test/cmi/aicc_cmi.spec.js +0 -684
  119. package/test/cmi/scorm2004_cmi.spec.js +0 -1066
  120. package/test/cmi_helpers.js +0 -161
  121. package/test/exceptions.spec.js +0 -71
  122. package/test/field_values.js +0 -353
  123. package/test/utilities.spec.js +0 -339
  124. package/webpack.js +0 -78
@@ -0,0 +1,154 @@
1
+ import APIConstants from "./constants/api_constants";
2
+
3
+ const scorm12_errors = APIConstants.scorm12.error_descriptions;
4
+ const aicc_errors = APIConstants.aicc.error_descriptions;
5
+ const scorm2004_errors = APIConstants.scorm2004.error_descriptions;
6
+
7
+ type APIError = {
8
+ errorCode: number;
9
+ errorMessage: string;
10
+ detailedMessage: string;
11
+ };
12
+
13
+ export class BaseScormValidationError extends Error {
14
+ constructor(errorCode: number) {
15
+ super(errorCode.toString());
16
+ this._errorCode = errorCode;
17
+ this.name = "ScormValidationError";
18
+ }
19
+
20
+ private readonly _errorCode: number;
21
+
22
+ /**
23
+ * Getter for _errorCode
24
+ * @return {number}
25
+ */
26
+ get errorCode(): number {
27
+ return this._errorCode;
28
+ }
29
+
30
+ setMessage(message: string) {
31
+ this.message = message;
32
+ }
33
+ }
34
+
35
+ /**
36
+ * Base Validation Exception
37
+ */
38
+ export class ValidationError
39
+ extends BaseScormValidationError
40
+ implements APIError
41
+ {
42
+ /**
43
+ * Constructor to take in an error message and code
44
+ * @param {number} errorCode
45
+ * @param {string} errorMessage
46
+ * @param {string} detailedMessage
47
+ */
48
+ constructor(
49
+ errorCode: number,
50
+ errorMessage: string,
51
+ detailedMessage?: string,
52
+ ) {
53
+ super(errorCode);
54
+ this.setMessage(errorMessage);
55
+ this._errorMessage = errorMessage;
56
+ if (detailedMessage) {
57
+ this._detailedMessage = detailedMessage;
58
+ }
59
+ }
60
+
61
+ private readonly _errorMessage: string;
62
+ private readonly _detailedMessage: string = "";
63
+
64
+ /**
65
+ * Getter for _errorMessage
66
+ * @return {string}
67
+ */
68
+ get errorMessage(): string {
69
+ return this._errorMessage;
70
+ }
71
+
72
+ /**
73
+ * Getter for _detailedMessage
74
+ * @return {string}
75
+ */
76
+ get detailedMessage(): string {
77
+ return this._detailedMessage;
78
+ }
79
+ }
80
+
81
+ /**
82
+ * SCORM 1.2 Validation Error
83
+ */
84
+ export class Scorm12ValidationError extends ValidationError {
85
+ /**
86
+ * Constructor to take in an error code
87
+ * @param {number} errorCode
88
+ */
89
+ constructor(errorCode: number) {
90
+ if ({}.hasOwnProperty.call(scorm12_errors, String(errorCode))) {
91
+ super(
92
+ errorCode,
93
+ scorm12_errors[String(errorCode)].basicMessage,
94
+ scorm12_errors[String(errorCode)].detailMessage,
95
+ );
96
+ } else {
97
+ super(
98
+ 101,
99
+ scorm12_errors["101"].basicMessage,
100
+ scorm12_errors["101"].detailMessage,
101
+ );
102
+ }
103
+ }
104
+ }
105
+
106
+ /**
107
+ * AICC Validation Error
108
+ */
109
+ export class AICCValidationError extends ValidationError {
110
+ /**
111
+ * Constructor to take in an error code
112
+ * @param {number} errorCode
113
+ */
114
+ constructor(errorCode: number) {
115
+ if ({}.hasOwnProperty.call(aicc_errors, String(errorCode))) {
116
+ super(
117
+ errorCode,
118
+ aicc_errors[String(errorCode)].basicMessage,
119
+ aicc_errors[String(errorCode)].detailMessage,
120
+ );
121
+ } else {
122
+ super(
123
+ 101,
124
+ aicc_errors["101"].basicMessage,
125
+ aicc_errors["101"].detailMessage,
126
+ );
127
+ }
128
+ }
129
+ }
130
+
131
+ /**
132
+ * SCORM 2004 Validation Error
133
+ */
134
+ export class Scorm2004ValidationError extends ValidationError {
135
+ /**
136
+ * Constructor to take in an error code
137
+ * @param {number} errorCode
138
+ */
139
+ constructor(errorCode: number) {
140
+ if ({}.hasOwnProperty.call(scorm2004_errors, String(errorCode))) {
141
+ super(
142
+ errorCode,
143
+ scorm2004_errors[String(errorCode)].basicMessage,
144
+ scorm2004_errors[String(errorCode)].detailMessage,
145
+ );
146
+ } else {
147
+ super(
148
+ 101,
149
+ scorm2004_errors["101"].basicMessage,
150
+ scorm2004_errors["101"].detailMessage,
151
+ );
152
+ }
153
+ }
154
+ }
@@ -1,3 +1,3 @@
1
- import AICC from '../AICC';
1
+ import AICC from "../AICC.ts";
2
2
 
3
3
  window.AICC = AICC;
@@ -1,6 +1,6 @@
1
- import Scorm2004API from '../Scorm2004API';
2
- import Scorm12API from '../Scorm12API';
3
- import AICC from '../AICC';
1
+ import Scorm2004API from "../Scorm2004API.ts";
2
+ import Scorm12API from "../Scorm12API.ts";
3
+ import AICC from "../AICC.ts";
4
4
 
5
5
  window.Scorm12API = Scorm12API;
6
6
  window.Scorm2004API = Scorm2004API;
@@ -1,3 +1,3 @@
1
- import Scorm12API from '../Scorm12API';
1
+ import Scorm12API from "../Scorm12API.ts";
2
2
 
3
3
  window.Scorm12API = Scorm12API;
@@ -1,3 +1,3 @@
1
- import Scorm2004API from '../Scorm2004API';
1
+ import Scorm2004API from "../Scorm2004API.ts";
2
2
 
3
3
  window.Scorm2004API = Scorm2004API;
@@ -0,0 +1,42 @@
1
+ import BaseAPI from "../BaseAPI";
2
+
3
+ /**
4
+ * Private class that wraps a timeout call to the commit() function
5
+ */
6
+ export class ScheduledCommit {
7
+ private _API;
8
+ private _cancelled = false;
9
+ private readonly _timeout;
10
+ private readonly _callback;
11
+
12
+ /**
13
+ * Constructor for ScheduledCommit
14
+ * @param {BaseAPI} API
15
+ * @param {number} when
16
+ * @param {string} callback
17
+ */
18
+ constructor(API: BaseAPI, when: number, callback: string) {
19
+ this._API = API;
20
+ this._timeout = setTimeout(this.wrapper.bind(this), when);
21
+ this._callback = callback;
22
+ }
23
+
24
+ /**
25
+ * Cancel any currently scheduled commit
26
+ */
27
+ cancel() {
28
+ this._cancelled = true;
29
+ if (this._timeout) {
30
+ clearTimeout(this._timeout);
31
+ }
32
+ }
33
+
34
+ /**
35
+ * Wrap the API commit call to check if the call has already been cancelled
36
+ */
37
+ wrapper() {
38
+ if (!this._cancelled) {
39
+ (async () => await this._API.commit(this._callback))();
40
+ }
41
+ }
42
+ }
@@ -0,0 +1,35 @@
1
+ import { RefObject, ResultObject } from "../types/api_types";
2
+ import { BaseCMI } from "../cmi/common/base_cmi";
3
+
4
+ export interface IBaseAPI {
5
+ cmi: BaseCMI;
6
+ startingData?: RefObject;
7
+
8
+ initialize(
9
+ callbackName: string,
10
+ initializeMessage?: string,
11
+ terminationMessage?: string,
12
+ ): string;
13
+ lmsInitialize(): string;
14
+ lmsFinish(): string;
15
+ lmsGetValue(CMIElement: string): string;
16
+ lmsSetValue(CMIElement: string, value: any): string;
17
+ lmsCommit(): string;
18
+ lmsGetLastError(): string;
19
+ lmsGetErrorString(CMIErrorCode: string | number): string;
20
+ lmsGetDiagnostic(CMIErrorCode: string | number): string;
21
+ storeData(_calculateTotalTime: boolean): Promise<ResultObject>;
22
+ renderCommitCMI(_terminateCommit: boolean): RefObject | Array<any>;
23
+ getLmsErrorMessageDetails(
24
+ _errorNumber: string | number,
25
+ _detail?: boolean,
26
+ ): string;
27
+ getCMIValue(_CMIElement: string): string;
28
+ setCMIValue(_CMIElement: string, _value: any): string;
29
+ validateCorrectResponse(_CMIElement: string, _value: any): void;
30
+ getChildElement(
31
+ _CMIElement: string,
32
+ _value: any,
33
+ _foundFirstIndex: boolean,
34
+ ): BaseCMI | null;
35
+ }
@@ -0,0 +1,32 @@
1
+ export type Settings = {
2
+ autocommit?: boolean;
3
+ autocommitSeconds?: number;
4
+ asyncCommit?: boolean;
5
+ sendFullCommit?: boolean;
6
+ lmsCommitUrl?: boolean | string;
7
+ dataCommitFormat?: string;
8
+ commitRequestDataType?: string;
9
+ autoProgress?: boolean;
10
+ logLevel?: number;
11
+ selfReportSessionTime?: boolean;
12
+ alwaysSendTotalTime?: boolean;
13
+ strict_errors?: boolean;
14
+ xhrHeaders?: RefObject;
15
+ xhrWithCredentials?: boolean;
16
+ responseHandler?: (response: Response) => Promise<ResultObject>;
17
+ requestHandler?: (commitObject: any) => any;
18
+ onLogMessage?: (messageLevel: number, logMessage: string) => void;
19
+ scoItemIds?: string[];
20
+ scoItemIdValidator?: false | ((scoItemId: string) => boolean);
21
+ mastery_override?: boolean;
22
+ };
23
+
24
+ export type RefObject = {
25
+ [key: string]: any;
26
+ };
27
+
28
+ export type ResultObject = {
29
+ result: string;
30
+ errorCode: number;
31
+ navRequest?: string;
32
+ };
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Debounce function to delay the execution of a given function.
3
+ *
4
+ * @param func - The function to debounce.
5
+ * @param wait - The number of milliseconds to delay.
6
+ * @param immediate - If `true`, the function will be triggered on the leading edge instead of the trailing.
7
+ * @returns A debounced version of the provided function.
8
+ */
9
+ export function debounce<T extends (...args: any[]) => void>(
10
+ func: T,
11
+ wait: number,
12
+ immediate = false,
13
+ ): (...args: Parameters<T>) => void {
14
+ let timeout: ReturnType<typeof setTimeout> | null;
15
+
16
+ return function (this: any, ...args: Parameters<T>) {
17
+ const context = this;
18
+
19
+ const later = () => {
20
+ timeout = null;
21
+ if (!immediate) func.apply(context, args);
22
+ };
23
+
24
+ const callNow = immediate && !timeout;
25
+
26
+ if (timeout) clearTimeout(timeout);
27
+ timeout = setTimeout(later, wait);
28
+
29
+ if (callNow) func.apply(context, args);
30
+ };
31
+ }
@@ -0,0 +1,338 @@
1
+ export const SECONDS_PER_SECOND = 1.0;
2
+ export const SECONDS_PER_MINUTE = 60;
3
+ export const SECONDS_PER_HOUR = 60 * SECONDS_PER_MINUTE;
4
+ export const SECONDS_PER_DAY = 24 * SECONDS_PER_HOUR;
5
+
6
+ type Designation = {
7
+ [key: string]: number;
8
+ };
9
+
10
+ type StringKeyMap = {
11
+ [key: string]: any;
12
+ };
13
+
14
+ const designations: Designation = {
15
+ D: SECONDS_PER_DAY,
16
+ H: SECONDS_PER_HOUR,
17
+ M: SECONDS_PER_MINUTE,
18
+ S: SECONDS_PER_SECOND,
19
+ };
20
+
21
+ /**
22
+ * Converts a Number to a String of HH:MM:SS
23
+ *
24
+ * @param {number} totalSeconds
25
+ * @return {string}
26
+ */
27
+ export function getSecondsAsHHMMSS(totalSeconds: number | null): string {
28
+ // SCORM spec does not deal with negative durations, give zero back
29
+ if (!totalSeconds || totalSeconds <= 0) {
30
+ return "00:00:00";
31
+ }
32
+
33
+ const hours = Math.floor(totalSeconds / SECONDS_PER_HOUR);
34
+ const dateObj = new Date(totalSeconds * 1000);
35
+ const minutes = dateObj.getUTCMinutes();
36
+ // make sure we add any possible decimal value
37
+ const seconds = dateObj.getSeconds();
38
+ const ms = totalSeconds % 1.0;
39
+ let msStr = "";
40
+
41
+ if (countDecimals(ms) > 0) {
42
+ if (countDecimals(ms) > 2) {
43
+ msStr = ms.toFixed(2);
44
+ } else {
45
+ msStr = String(ms);
46
+ }
47
+
48
+ msStr = "." + msStr.split(".")[1];
49
+ }
50
+
51
+ return (
52
+ (hours + ":" + minutes + ":" + seconds).replace(/\b\d\b/g, "0$&") + msStr
53
+ );
54
+ }
55
+
56
+ /**
57
+ * Calculate the number of seconds from ISO 8601 Duration
58
+ *
59
+ * @param {number} seconds
60
+ * @return {string}
61
+ */
62
+ export function getSecondsAsISODuration(seconds: number | null): string {
63
+ // SCORM spec does not deal with negative durations, give zero back
64
+ if (!seconds || seconds <= 0) {
65
+ return "PT0S";
66
+ }
67
+
68
+ let duration = "P";
69
+ let remainder = seconds;
70
+ for (const designationsKey in designations) {
71
+ const current_seconds = designations[designationsKey];
72
+ let value = Math.floor(remainder / current_seconds);
73
+ remainder = remainder % current_seconds;
74
+
75
+ if (countDecimals(remainder) > 2) {
76
+ remainder = Number(Number(remainder).toFixed(2));
77
+ }
78
+
79
+ // If we have anything left in the remainder, and we're currently adding
80
+ // seconds to the duration, go ahead and add the decimal to the seconds
81
+ if (designationsKey === "S" && remainder > 0) {
82
+ value += remainder;
83
+ }
84
+
85
+ if (value) {
86
+ if (
87
+ (duration.indexOf("D") > 0 ||
88
+ designationsKey === "H" ||
89
+ designationsKey === "M" ||
90
+ designationsKey === "S") &&
91
+ duration.indexOf("T") === -1
92
+ ) {
93
+ duration += "T";
94
+ }
95
+
96
+ duration += `${value}${designationsKey}`;
97
+ }
98
+ }
99
+ return duration;
100
+ }
101
+
102
+ /**
103
+ * Calculate the number of seconds from HH:MM:SS.DDDDDD
104
+ *
105
+ * @param {string} timeString
106
+ * @param {RegExp} timeRegex
107
+ * @return {number}
108
+ */
109
+ export function getTimeAsSeconds(
110
+ timeString: string | number | boolean | null,
111
+ timeRegex: RegExp | string,
112
+ ): number {
113
+ if (typeof timeString === "number" || typeof timeString === "boolean") {
114
+ timeString = String(timeString);
115
+ }
116
+ if (typeof timeRegex === "string") {
117
+ timeRegex = new RegExp(timeRegex);
118
+ }
119
+ if (!timeString || !timeString.match(timeRegex)) {
120
+ return 0;
121
+ }
122
+
123
+ const parts = timeString.split(":");
124
+ const hours = Number(parts[0]);
125
+ const minutes = Number(parts[1]);
126
+ const seconds = Number(parts[2]);
127
+ return hours * 3600 + minutes * 60 + seconds;
128
+ }
129
+
130
+ /**
131
+ * Calculate the number of seconds from ISO 8601 Duration
132
+ *
133
+ * @param {string} duration
134
+ * @param {RegExp} durationRegex
135
+ * @return {number}
136
+ */
137
+ export function getDurationAsSeconds(
138
+ duration: string | null,
139
+ durationRegex: RegExp | string,
140
+ ): number {
141
+ if (typeof durationRegex === "string") {
142
+ durationRegex = new RegExp(durationRegex);
143
+ }
144
+
145
+ if (!duration || !duration.match(durationRegex)) {
146
+ return 0;
147
+ }
148
+
149
+ const [, years, _, , days, hours, minutes, seconds] =
150
+ new RegExp(durationRegex).exec(duration) || [];
151
+ let result = 0.0;
152
+ result += Number(seconds) || 0.0;
153
+ result += Number(minutes) * 60.0 || 0.0;
154
+ result += Number(hours) * 3600.0 || 0.0;
155
+ result += Number(days) * (60 * 60 * 24.0) || 0.0;
156
+ result += Number(years) * (60 * 60 * 24 * 365.0) || 0.0;
157
+ return result;
158
+ }
159
+
160
+ /**
161
+ * Adds together two ISO8601 Duration strings
162
+ *
163
+ * @param {string} first
164
+ * @param {string} second
165
+ * @param {RegExp|string} durationRegex
166
+ * @return {string}
167
+ */
168
+ export function addTwoDurations(
169
+ first: string,
170
+ second: string,
171
+ durationRegex: RegExp | string,
172
+ ): string {
173
+ const regex: RegExp =
174
+ typeof durationRegex === "string"
175
+ ? new RegExp(durationRegex)
176
+ : durationRegex;
177
+ return getSecondsAsISODuration(
178
+ getDurationAsSeconds(first, regex) + getDurationAsSeconds(second, regex),
179
+ );
180
+ }
181
+
182
+ /**
183
+ * Add together two HH:MM:SS.DD strings
184
+ *
185
+ * @param {string} first
186
+ * @param {string} second
187
+ * @param {RegExp} timeRegex
188
+ * @return {string}
189
+ */
190
+ export function addHHMMSSTimeStrings(
191
+ first: string,
192
+ second: string,
193
+ timeRegex: RegExp | string,
194
+ ): string {
195
+ if (typeof timeRegex === "string") {
196
+ timeRegex = new RegExp(timeRegex);
197
+ }
198
+ return getSecondsAsHHMMSS(
199
+ getTimeAsSeconds(first, timeRegex) + getTimeAsSeconds(second, timeRegex),
200
+ );
201
+ }
202
+
203
+ /**
204
+ * Flatten a JSON object down to string paths for each values
205
+ * @param {object} data
206
+ * @return {object}
207
+ */
208
+ export function flatten(data: StringKeyMap): object {
209
+ const result: StringKeyMap = {};
210
+
211
+ /**
212
+ * Recurse through the object
213
+ * @param {*} cur
214
+ * @param {*} prop
215
+ */
216
+ function recurse(cur: any, prop: any) {
217
+ if (Object(cur) !== cur) {
218
+ result[prop] = cur;
219
+ } else if (Array.isArray(cur)) {
220
+ for (let i = 0, l = cur.length; i < l; i++) {
221
+ recurse(cur[i], prop + "[" + i + "]");
222
+ if (l === 0) result[prop] = [];
223
+ }
224
+ } else {
225
+ let isEmpty = true;
226
+
227
+ for (const p in cur) {
228
+ if ({}.hasOwnProperty.call(cur, p)) {
229
+ isEmpty = false;
230
+ recurse(cur[p], prop ? prop + "." + p : p);
231
+ }
232
+ }
233
+
234
+ if (isEmpty && prop) result[prop] = {};
235
+ }
236
+ }
237
+
238
+ recurse(data, "");
239
+ return result;
240
+ }
241
+
242
+ /**
243
+ * Un-flatten a flat JSON object
244
+ * @param {object} data
245
+ * @return {object}
246
+ */
247
+ export function unflatten(data: StringKeyMap): object {
248
+ "use strict";
249
+
250
+ if (Object(data) !== data || Array.isArray(data)) return data;
251
+ const regex = /\.?([^.[\]]+)|\[(\d+)]/g;
252
+ const result: StringKeyMap = {};
253
+
254
+ for (const p in data) {
255
+ if ({}.hasOwnProperty.call(data, p)) {
256
+ let cur = result;
257
+ let prop = "";
258
+ let m = regex.exec(p);
259
+
260
+ while (m) {
261
+ cur = cur[prop] || (cur[prop] = m[2] ? [] : {});
262
+ prop = m[2] || m[1];
263
+ m = regex.exec(p);
264
+ }
265
+
266
+ cur[prop] = data[p];
267
+ }
268
+ }
269
+
270
+ return result[""] || result;
271
+ }
272
+
273
+ /**
274
+ * Counts the number of decimal places
275
+ * @param {number} num
276
+ * @return {number}
277
+ */
278
+ export function countDecimals(num: number): number {
279
+ if (Math.floor(num) === num || String(num).indexOf(".") < 0) return 0;
280
+ const parts = num.toString().split(".")[1];
281
+ return parts.length || 0;
282
+ }
283
+
284
+ /**
285
+ * Formats the SCORM messages for easy reading
286
+ *
287
+ * @param {string} functionName
288
+ * @param {string} message
289
+ * @param {string} CMIElement
290
+ * @return {string}
291
+ */
292
+ export function formatMessage(
293
+ functionName: string,
294
+ message: string,
295
+ CMIElement?: string,
296
+ ): string {
297
+ const baseLength = 20;
298
+ let messageString = "";
299
+
300
+ messageString += functionName;
301
+
302
+ let fillChars = baseLength - messageString.length;
303
+
304
+ for (let i = 0; i < fillChars; i++) {
305
+ messageString += " ";
306
+ }
307
+
308
+ messageString += ": ";
309
+
310
+ if (CMIElement) {
311
+ const CMIElementBaseLength = 70;
312
+
313
+ messageString += CMIElement;
314
+
315
+ fillChars = CMIElementBaseLength - messageString.length;
316
+
317
+ for (let j = 0; j < fillChars; j++) {
318
+ messageString += " ";
319
+ }
320
+ }
321
+
322
+ if (message) {
323
+ messageString += message;
324
+ }
325
+
326
+ return messageString;
327
+ }
328
+
329
+ /**
330
+ * Checks to see if {str} contains {tester}
331
+ *
332
+ * @param {string} str String to check against
333
+ * @param {string} tester String to check for
334
+ * @return {boolean}
335
+ */
336
+ export function stringMatches(str: string, tester: string): boolean {
337
+ return str?.match(tester) !== null;
338
+ }
package/tea.yaml ADDED
@@ -0,0 +1,6 @@
1
+ # https://tea.xyz/what-is-this-file
2
+ ---
3
+ version: 1.0.0
4
+ codeOwners:
5
+ - '0x1bfc0b2fc65c3eEE1AfFB43E07fbD8F00E18B93E'
6
+ quorum: 1