PageSourceSearch

https://pathophysiology-basics-common-cold.netlify.app/lms/scormdriver.js

js pathophysiology-basics-common-cold.netlify.app collected 2026-10-03 11:14:48 UTC 1,120,274 bytes, 34,386 lines download raw bytes

1
2var VERSION = "7.12.0";
3
4var PREFERENCE_DEFAULT = 0;
5var PREFERENCE_OFF     = -1;
6var PREFERENCE_ON      = 1;
7
8var LESSON_STATUS_PASSED        = 1;    //the user completed the content with a score sufficient to pass
9var LESSON_STATUS_COMPLETED     = 2;    //the user completed the content
10var LESSON_STATUS_FAILED        = 3;    //the user completed the content but his/her score was not sufficient to pass
11var LESSON_STATUS_INCOMPLETE    = 4;    //the user began the content but did not complete it
12var LESSON_STATUS_BROWSED       = 5;    //the user looked at the content but was not making a recorded attempt at it
13var LESSON_STATUS_NOT_ATTEMPTED = 6;    //the user has not started the content
14
15var ENTRY_REVIEW     = 1;
16var ENTRY_FIRST_TIME = 2;
17var ENTRY_RESUME     = 3;
18
19var MODE_NORMAL = 1;
20var MODE_BROWSE = 2;
21var MODE_REVIEW = 3;
22
23var MAX_CMI_TIME = 36002439990; //max CMI Timespan 9999:99:99.99
24
25var NO_ERROR = 0;
26var ERROR_LMS = 1;
27var ERROR_INVALID_PREFERENCE = 2;
28var ERROR_INVALID_NUMBER = 3;
29var ERROR_INVALID_ID = 4;
30var ERROR_INVALID_STATUS = 5;
31var ERROR_INVALID_RESPONSE = 6;
32var ERROR_NOT_LOADED = 7;
33var ERROR_INVALID_INTERACTION_RESPONSE = 8;
34
35var EXIT_TYPE_SUSPEND = "SUSPEND";
36var EXIT_TYPE_FINISH  = "FINISH";
37var EXIT_TYPE_TIMEOUT = "TIMEOUT";
38var EXIT_TYPE_UNLOAD  = "UNLOAD";
39
40var INTERACTION_RESULT_CORRECT  = "CORRECT";
41var INTERACTION_RESULT_WRONG  = "WRONG";
42var INTERACTION_RESULT_UNANTICIPATED  = "UNANTICIPATED";
43var INTERACTION_RESULT_NEUTRAL = "NEUTRAL";
44
45var INTERACTION_TYPE_TRUE_FALSE = "true-false";
46var INTERACTION_TYPE_CHOICE = "choice";
47var INTERACTION_TYPE_FILL_IN = "fill-in";
48var INTERACTION_TYPE_LONG_FILL_IN = "long-fill-in";
49var INTERACTION_TYPE_MATCHING = "matching";
50var INTERACTION_TYPE_PERFORMANCE = "performance";
51var INTERACTION_TYPE_SEQUENCING = "sequencing";
52var INTERACTION_TYPE_LIKERT = "likert";
53var INTERACTION_TYPE_NUMERIC = "numeric";
54
55var DATA_CHUNK_PAIR_SEPARATOR = '###';
56var DATA_CHUNK_VALUE_SEPARATOR = '$$';
57
58//Configuration Parameters
59var APPID = "__APPID__";                            //this is used for SCORM Cloud. Set to "" to disable.
60var CLOUDURL = "__CLOUDURL__";
61
62var blnDebug = true;                        //set this to false if you don't want the overhead of recording debug information
63
64var strLMSStandard = "__LMS_STANDARD__";    //used in versions that support multiple standards, set to "NONE" to default
65                                            //to StandAlone mode. Possible values = "AUTO", "NONE", "SCORM", "AICC", "SCORM2004", "TCAPI", "CMI5"
66                                            //AUTO mode will automatically determine the best standard to use
67                                            //(it first tries AICC based on "AICC_URL" query param, then TCAPI and CMI5 based on "endpoint" and other
68                                            //query parameters, then SCORM 2004, then SCORM 1.2/1.1 then NONE
69
70var DEFAULT_EXIT_TYPE = EXIT_TYPE_SUSPEND;  //When the content is unloaded without an API function indicating the type of exit,
71                                            //what default behavior do you want to assume.  Use EXIT_TYPE_SUSPEND if you plan to
72                                            //call Finish when the content is complete.  Use EXIT_TYPE_FINISH if you do not plan
73                                            //to call Finish.
74
75
76var AICC_LESSON_ID = "1";                   //if recording question answers in AICC in an LMS that supports interactions,
77                                            //this field need to match the system_id on line in the .DES file that describes
78                                            //this course, the default is 1. Be sure that this value does not contain double quote characters (")
79
80var EXIT_BEHAVIOR = "SCORM_RECOMMENDED";        //used to control window closing behavior on call of ConcedeControl
81                                                //Possible Values: SCORM_RECOMMENDED, ALWAYS_CLOSE, ALWAYS_CLOSE_TOP, ALWAYS_CLOSE_PARENT,
82                                                //                 NOTHING, REDIR_CONTENT_FRAME, LMS_SPECIFIED_REDIRECT
83                                                //
84                                                // Not used for cmi5
85
86var EXIT_TARGET = "lms/goodbye.html";       //Used in conjunction with EXIT_BEHAVIOR, only with REDIR_CONTENT_FRAME. This should be a neutral page that is displayed
87                                            //after the course has exited, but before it has been taked away by the LMS
88
89var AICC_COMM_DISABLE_XMLHTTP = false;      //false is the preferred value, true can be required in certain cross domain situations
90var AICC_COMM_DISABLE_IFRAME = false;       //false is the preferred value, true can be required in certain cross domain situations
91
92var AICC_COMM_PREPEND_HTTP_IF_MISSING = true;       //Some AICC LMS's will omit the "http://" from the AICC_URL value. If this is the case,
93                                                    //set this setting to true to have the API prepe
93nd the "http://" value
94
95var AICC_REPORT_MIN_MAX_SCORE = true;       //Some AICC LMS's have trouble processing a score which contains a min and max value. Setting this
96                                            //value to false allows you to turn off that reporting to accommodate those LMS's.
97
98
99var SHOW_DEBUG_ON_LAUNCH = false;       //set this to true when debugging to force the debug window to launch immediately
100
101var DO_NOT_REPORT_INTERACTIONS = false;     //set this to true to disable reporting of question results to the LMS as some LMS's
102                                            //particularly those supporting only AICC can have problems with interactions results
103
104
105var SCORE_CAN_ONLY_IMPROVE = false;         //set this to true to ensure that on subsequent attempts, a learner's score can only go up
106
107var REVIEW_MODE_IS_READ_ONLY = true;        //set this to true if no new data should be saved when a course is launched in review mode (normally this is the LMS's responsibility)
108
109
110var TCAPI_DONT_USE_BROKEN_URN_IDS = true;       //Prior to 7.0, there was a regression that caused some activity IDs to be invalid URIs. With this flag set to true,
111                                                //the IDs will be set to valid URIs. However, this will cause a difference of IDs between interactions from before
112                                                //7.0 and after. Therefore, set this to false when running any courses that were recorded using a version of Driver earlier than 7.0.
113
114
115/*
116These variables control how long the API should wait on an AICC form submission before timing out
117AICC_RE_CHECK_LOADED_INTERVAL = Number of milliseconds the API waits between checks to see if the form is loaded
118AICC_RE_CHECK_ATTEMPTS_BEFORE_TIMEOUT = Number of times the API checks to see if the form is loaded
119AICC_RE_CHECK_LOADED_INTERVAL * AICC_RE_CHECK_ATTEMPTS_BEFORE_TIMEOUT = Desired time out in milliseconds
120*/
121var AICC_RE_CHECK_LOADED_INTERVAL = 250;
122var AICC_RE_CHECK_ATTEMPTS_BEFORE_TIMEOUT = 240;
123
124var USE_AICC_KILL_TIME = true;              //set this to false to disable the explicit extra waiting between AICC requests
125
126//This controls the entry default when it is not set by the LMS
127//Possible options are
128//ENTRY_REVIEW (normal default) - ENTRY_FIRST_TIME - ENTRY_RESUME
129var AICC_ENTRY_FLAG_DEFAULT = ENTRY_REVIEW;
130
131var AICC_USE_CUSTOM_COMMS = false; //Used to force the AICC communications through a custom routine (defined in indexAPI.html) instead of other methods
132
133var FORCED_COMMIT_TIME = "60000"; //Used to force CommitData back to the LMS at the desired interval (in milliseconds). Set to 0 (zero) to not force a commit time.
134
135var ALLOW_NONE_STANDARD = true; //Set this to false to show an error message if no LMS API can be found - Standalone will still work fine.
136
137var USE_2004_SUSPENDALL_NAVREQ = false;
138var USE_STRICT_SUSPEND_DATA_LIMITS = false; //determines if we check the suspend_data length in Driver
139var EXIT_SUSPEND_IF_COMPLETED = false; //determines if Suspend is sent for exit when SetReachedEnd is called - legacy driver defaulted to false
140var EXIT_NORMAL_IF_PASSED = false; //determines if exit is Normal when Passed - legacy driver defaulted to false and always sends suspend
141var AICC_ENCODE_PARAMETER_VALUES = true; //default is true;
142
143var PASS_FAIL_SETS_COMPLETION_FOR_2004 = true;  //determines if SetPassed and SetFailed also set completion for SCORM 2004
144var ALLOW_INTERACTION_NULL_LEARNER_RESPONSE = false;  //determines whether learner response is required when recording interactions
145var PREVENT_STATUS_CHANGE_DURING_INIT = false;  //when true prevents initialization process from setting complete/success status from LMS result
146var USE_LEGACY_IDENTIFIERS_FOR_2004 = true; //set to `true` to disable conversion of identifiers to proper URIs (may make content non-compliant with SCORM spec)
147var URI_IDENTIFIER_PREFIX = "urn:scormdriver:"; //used to make proper URI identifiers out of identifiers that are detected as non-absolute URIs
148
149window.scormdriver_content = window;
150
151function LoadContent() {}
152
153function GetQueryStringValue(strElement, strQueryString){
154
155    var aryPairs;
156    var foundValue;
157
158    //WriteToDebug("In GetQueryStringValue strElement=" + strElement + ", strQueryString=" + strQueryString);
159
160    //get rid of the leading "?"
161    strQueryString = strQueryString.substring(1);
162
163    //split into name/value pairs
164    aryPairs = strQueryString.split("&");
165
166    foundValue = SearchQueryStringPairs(aryPairs, strElement);
167
168    if (foundValue === null){
169        //sometimes we wind up with a querystring that looks like "indexAPI.html?ShowDebug=true?AICC_URL=http://www.blah.com/blahblah&AICC_SID=12345"
170        //try to handle this case by splitting on both ? and &
171
172        aryPairs = strQueryString.split(/[\?\&]/);
173        foundValue = SearchQueryStringPairs(aryPairs, strElement);
174    }
175
176    if (foundValue === null){
177        //if we didn't find a match, return an empty string
178        WriteToDebug("GetQueryStringValue Element '" + strElement + "' Not Found, Returning: empty string");
179        return "";
180    }
181    else{
182        WriteToDebug("GetQueryStringValue for '" + strElement + "' Returning: " + foundValue);
183        return foundValue;
184    }
185}
186
187function SearchQueryStringPairs(aryPairs, strElement){
188
189    var i;
190    var intEqualPos;
191    var strArg = "";
192    var strValue = "";
193
194    strElement = strElement.toLowerCase();
195
196    //search each querystring value and return the first match
197    for (i=0; i < aryPairs.length; i++){
198
199        //WriteToDebug("Looking at element-" + aryPairs[i]);
200
201        intEqualPos = aryPairs[i].indexOf('=');
202
203        if (intEqualPos != -1){
204
205            strArg = aryPairs[i].substring(0,intEqualPos);
206
207            //WriteToDebug("Arg=" + strArg);
208
209            if (EqualsIgnoreCase(strArg, strElement)){
210
211                //WriteToDebug("Found Match");
212                strValue = aryPairs[i].substring(intEqualPos+1);
213
214                //WriteToDebug("Decoding value-" + strValue);
215
216                strValue = new String(strValue)
217
218                strValue = strValue.replace(/\+/g, "%20")
219                strValue = unescape(strValue);
220
221                //WriteToDebug("Returning value-" + strValue);
222
223                return new String(strValue);
224            }
225        }
226    }
227
228    return null;
229}
230
231function ConvertStringToBoolean(str){
232
233    var intTemp;
234
235    //WriteToDebug("In ConvertStringToBoolean");
236
237    if (EqualsIgnoreCase(str, "true") || EqualsIgnoreCase(str, "t") || str.toLowerCase().indexOf("t") == 0){
238        //WriteToDebug("Found alpha true");
239        return true;
240    }
241    else{
242        intTemp = parseInt(str, 10);
243        if (intTemp == 1 || intTemp==-1){
244            //WriteToDebug("Found numeric true");
245             return true;
246        }
247        else{
248            //WriteToDebug("Returning false");
249            return false;
250        }
251    }
252}
253
254
255
256function EqualsIgnoreCase(str1, str2){
257
258    //WriteToDebug("In EqualsIgnoreCase str1=" + str1 + ", str2=" + str2);
259
260    var blnReturn;
261
262    str1 = new String(str1);
263    str2 = new String(str2);
264
265    blnReturn = (str1.toLowerCase() == str2.toLowerCase())
266
267    //WriteToDebug("Returning " + blnReturn);
268
269    return blnReturn;
270}
271
272
273function ValidInteger(intNum){
274
275    WriteToDebug("In ValidInteger intNum=" + intNum);
276
277    var str = new String(intNum);
278
279    if (str.indexOf("-", 0) == 0){
280        str = str.substring(1, str.length - 1);
281    }
282
283    var regValidChars= new RegExp("[^0-9]");
284
285    if (str.search(regValidChars) == -1){
286        WriteToDebug("Returning true");
287        return true;
288    }
289
290    WriteToDebug("Returning false");
291    return false;
292}
293
294
295//---------------------------------------------------------------------------------
296//Time Manipulation Functions
297
298function ConvertDateToIso8601TimeStamp(dtm){
299
300    var strTimeStamp;
301
302    dtm = new Date(dtm);
303
304    var Year   = dtm.getFullYear();
305    var Month  = dtm.getMonth() + 1;
306    var Day    = dtm.getDate();
307    var Hour   = dtm.getHours();
308    var Minute = dtm.getMinutes();
309    var Second = dtm.getSeconds();
310
311    Month  = ZeroPad(Month, 2);
312    Day    = ZeroPad(Day, 2);
313    Hour   = ZeroPad(Hour, 2);
314    Minute = ZeroPad(Minute, 2);
315    Second = ZeroPad(Second, 2);
316
317    strTimeStamp = Year + "-" + Month + "-" + Day + "T" + Hour + ":" + Minute + ":" + Second;
318
319
320    //JBR 11-9-2011 - adding timezone offset
321    // should only be called for SCORM 2004+
322    var tzoffset = -(dtm.getTimezoneOffset()/60);
323    if(tzoffset!=0){
324        strTimeStamp += '.0'; // this seconds fraction is required if you have a TZOffset
325        if(tzoffset>0){
326            // handle the fraction offest in minutes
327            if((''+tzoffset).indexOf('.')!=-1){
328                var fraction = '0'+(''+tzoffset).substr((''+tzoffset).indexOf('.'),(''+tzoffset).length);
329                var base = (''+tzoffset).substr(0,(''+tzoffset).indexOf('.'));
330                fraction = (fraction*60);
331                strTimeStamp += '+'+ZeroPad(base+'.'+fraction,2);
332            }else{
333                // add the '+' if it's needed (the - is handled by ZeroPad)
334                strTimeStamp += '+'+ZeroPad(tzoffset,2);
335            }
336        }else{
337            strTimeStamp += ZeroPad(tzoffset,2);
338        }
339    }
340
341    return strTimeStamp;
342
343}
344
345function ConvertIso8601TimeStampToDate(strTimeStamp){
346
347    strTimeStamp = new String(strTimeStamp);
348    var ary = new Array();
349    ary = strTimeStamp.split(/[\:T+-]/);
350
351    var Year   = ary[0];
352    var Month  = ary[1]-1;
353    var Day    = ary[2];
354    var Hour   = ary[3];
355    var Minute = ary[4];
356    var Second = ary[5];
357
358
359    return new Date(Year, Month, Day, Hour, Minute, Second, 0);
360
361}
362
363function ConvertDateToCMIDate(dtmDate){
364
365    WriteToDebug("In ConvertDateToCMIDate");
366
367    var strYear;
368    var strMonth;
369    var strDay;
370    var strReturn;
371
372    dtmDate = new Date(dtmDate);
373
374    strYear = dtmDate.getFullYear()
375    strMonth = (dtmDate.getMonth() + 1);
376    strDay = dtmDate.getDate();
377
378    strReturn = ZeroPad(strYear, 4) + "/" + ZeroPad(strMonth, 2) + "/" + ZeroPad(strDay, 2);
379
380    return strReturn;
381}
382
383function ConvertDateToCMITime(dtmDate){
384
385    var strHours;
386    var strMinutes;
387    var strSeconds;
388    var strReturn;
389
390    dtmDate = new Date(dtmDate);
391
392    strHours = dtmDate.getHours();
393    strMinutes = dtmDate.getMinutes();
394    strSeconds = dtmDate.getSeconds();
395
396    strReturn = ZeroPad(strHours, 2) + ":" + ZeroPad(strMinutes, 2) + ":" + ZeroPad(strSeconds, 2);
397
398    return strReturn;
399}
400
401
402function ConvertCMITimeSpanToMS(strTime){
403
404    WriteToDebug("In ConvertCMITimeSpanToMS, strTime=" + strTime);
405
406    var aryParts;
407    var intHours;
408    var intMinutes;
409    var intSeconds;
410
411    var intTotalMilliSeconds;
412
413    //split the string into its parts
414    aryParts = strTime.split(":");
415
416    //make sure it's valid before we knock ourselves out
417    if (! IsValidCMITimeSpan(strTime)){
418        WriteToDebug("ERROR - Invalid TimeSpan");
419        SetErrorInfo(SCORM_ERROR_GENERAL, "LMS ERROR - Invalid time span passed to ConvertCMITimeSpanToMS, please contact technical support");
420        return 0;
421    }
422
423    //seperate the parts and multiply by the appropriate constant (3600000 = num milliseconds in an hour, etc)
424    intHours   = aryParts[0];
425    intMinutes = aryParts[1];
426    intSeconds = aryParts[2];   //don't need to worry about milliseconds b/c they are expressed as fractions of a second
427
428    WriteToDebug("intHours=" + intHours + " intMinutes=" + intMinutes + " intSeconds=" + intSeconds);
429
430    intTotalMilliSeconds = (intHours * 3600000) + (intMinutes * 60000) + (intSeconds * 1000);
431
432    //necessary because in JavaScript, some values for intSeconds (such as 2.01) will have a lot of decimal
433    //places when multiplied by 1000. For instance, 2.01 turns into 2009.999999999999997.
434    intTotalMilliSeconds = Math.round(intTotalMilliSeconds);
435
436    WriteToDebug("Returning " + intTotalMilliSeconds);
437
438    return intTotalMilliSeconds;
439}
440
441
442function ConvertScorm2004TimeToMS(strIso8601Time){
443
444    WriteToDebug("In ConvertScorm2004TimeToMS, strIso8601Time=" + strIso8601Time);
445
446    var intTotalMs = 0;
447
448    var strNumberBuilder;
449    var strCurrentCharacter;
450    var blnInTimeSection;
451
452
453    var Seconds = 0;    // 100 hundreths of a seconds
454    var Minutes = 0;    // 60 seconds
455    var Hours = 0;      // 60 minutes
456    var Days = 0;       // 24 hours
457    var Months = 0;     // assumed to be an "average" month (figures a leap year every 4 years) = ((365*4) + 1) / 48 days - 30.4375 days per month
458    var Years = 0;      // assumed to be 12 "average" months
459
460    var MILLISECONDS_PER_SECOND = 1000;
461    var MILLISECONDS_PER_MINUTE = MILLISECONDS_PER_SECOND * 60;
462    var MILLISECONDS_PER_HOUR   = MILLISECONDS_PER_MINUTE * 60;
463    var MILLISECONDS_PER_DAY    = MILLISECONDS_PER_HOUR * 24;
464    var MILLISECONDS_PER_MONTH  = MILLISECONDS_PER_DAY * (((365 * 4) + 1) / 48);
465    var MILLISECONDS_PER_YEAR   = MILLISECONDS_PER_MONTH * 12;
466
467
468    strIso8601Time = new String(strIso8601Time);
469
470    strNumberBuilder = "";
471    strCurrentCharacter = "";
472    blnInTimeSection = false;
473
474    //start at 1 to get past the "P"
475    for (var i=1; i < strIso8601Time.length; i++){
476
477        strCurrentCharacter = strIso8601Time.charAt(i);
478
479
480        if (IsIso8601SectionDelimiter(strCurrentCharacter)){
481
482            switch (strCurrentCharacter.toUpperCase()){
483
484                case "Y":
485                    Years = parseInt(strNumberBuilder, 10);
486                break;
487
488                case "M":
489                    if (blnInTimeSection){
490                        Minutes = parseInt(strNumberBuilder, 10);
491                    }
492                    else{
493                        Months = parseInt(strNumberBuilder, 10);
494                    }
495                break;
496
497                case "D":
498                    Days = parseInt(strNumberBuilder, 10);
499                break;
500
501                case "H":
502                    Hours = parseInt(strNumberBuilder, 10);
503                break;
504
505                case "S":
506                    Seconds = parseFloat(strNumberBuilder);
507                break;
508
509                case "T":
510                    blnInTimeSection = true;
511                break;
512
513            }
514
515            strNumberBuilder = "";
516        }
517        else{
518            strNumberBuilder += "" + strCurrentCharacter;       //use "" to keep the number as string concats instead of numeric additions
519        }
520
521    }
522
523    WriteToDebug("Years=" + Years + "\n" +
524                "Months=" + Months + "\n" +
525                "Days=" + Days + "\n" +
526                "Hours=" + Hours + "\n" +
527                "Minutes=" + Minutes + "\n" +
528                "Seconds=" + Seconds + "\n"
529                );
530
531    intTotalMs = (Years * MILLISECONDS_PER_YEAR) +
532                 (Months * MILLISECONDS_PER_MONTH) +
533                 (Days * MILLISECONDS_PER_DAY) +
534                 (Hours * MILLISECONDS_PER_HOUR) +
535                 (Minutes * MILLISECONDS_PER_MINUTE) +
536                 (Seconds * MILLISECONDS_PER_SECOND);
537
538    //necessary because in JavaScript, some values (such as 2.01) will have a lot of decimal
539    //places when multiplied by a larger number. For instance, 2.01 turns into 2009.999999999999997.
540    intTotalMs = Math.round(intTotalMs);
541
542    WriteToDebug ("returning-" + intTotalMs);
543
544    return intTotalMs;
545
546}
547
548function IsIso8601SectionDelimiter(str){
549
550    if (str.search(/[PYMDTHS]/) >=0 ){
551        return true;
552    }
553    else{
554        return false;
555    }
556
557}
558
559
560function IsValidCMITimeSpan(strValue){
561
562    WriteToDebug("In IsValidCMITimeSpan strValue=" + strValue);
563
564    //note that the spec does not say that minutes or seconds have to be < 60
565
566    var regValid = /^\d?\d?\d?\d:\d?\d:\d?\d(.\d\d?)?$/;
567
568    if (strValue.search(regValid) > -1){
569        WriteToDebug("Returning True");
570        return true;
571    }
572    else{
573        WriteToDebug("Returning False");
574        return false;
575    }
576}
577
578
579
580function IsValidIso8601TimeSpan(strValue){
581
582    WriteToDebug("In IsValidIso8601TimeSpan strValue=" + strValue);
583
584    var regValid = /^P(\d+Y)?(\d+M)?(\d+D)?(T(\d+H)?(\d+M)?(\d+(.\d\d?)?S)?)?$/;
585
586    if (strValue.search(regValid) > -1){
587        WriteToDebug("Returning True");
588        return true;
589    }
590    else{
591        WriteToDebug("Returning False");
592        return false;
593    }
594}
595
596function ConvertMilliSecondsToTCAPITime(intTotalMilliseconds, blnIncludeFraction){
597
598    var intHours;
599    var intMinutes;
600    var intSeconds;
601    var intMilliseconds;
602    var intHundredths;
603    var strCMITimeSpan;
604
605    WriteToDebug("In ConvertMilliSecondsToTCAPITime, intTotalMilliseconds = " + intTotalMilliseconds + ", blnIncludeFraction = " + blnIncludeFraction);
606
607    if (blnIncludeFraction == null || blnIncludeFraction == undefined){
608        blnIncludeFraction = true;
609    }
610
611    //extract time parts
612    intMilliseconds = intTotalMilliseconds % 1000;
613
614    intSeconds = ((intTotalMilliseconds - intMilliseconds) / 1000) % 60;
615
616    intMinutes = ((intTotalMilliseconds - intMilliseconds - (intSeconds * 1000)) / 60000) % 60;
617
618    intHours = (intTotalMilliseconds - intMilliseconds - (intSeconds * 1000) - (intMinutes * 60000)) / 3600000;
619
620    WriteToDebug("Separated Parts, intHours=" + intHours + ", intMinutes=" + intMinutes + ", intSeconds=" + intSeconds + ", intMilliseconds=" + intMilliseconds);
621
622    /*
623    deal with exceptional case when content used a huge amount of time and interpreted CMITimstamp
624    to allow a number of intMinutes and seconds greater than 60 i.e. 9999:99:99.99 instead of 9999:60:60:99
625    note - this case is permissable under SCORM, but will be exceptionally rare
626    */
627
628    if (intHours == 10000)
629    {
630        WriteToDebug("Max intHours detected");
631
632        intHours = 9999;
633
634        intMinutes = (intTotalMilliseconds - (intHours * 3600000)) / 60000;
635        if (intMinutes == 100)
636        {
637            intMinutes = 99;
638        }
639        intMinutes = Math.floor(intMinutes);
640
641        intSeconds = (intTotalMilliseconds - (intHours * 3600000) - (intMinutes * 60000)) / 1000;
642        if (intSeconds == 100)
643        {
644            intSeconds = 99;
645        }
646        intSeconds = Math.floor(intSeconds);
647
648        intMilliseconds = (intTotalMilliseconds - (intHours * 3600000) - (intMinutes * 60000) - (intSeconds * 1000));
649
650        WriteToDebug("Separated Parts, intHours=" + intHours + ", intMinutes=" + intMinutes + ", intSeconds=" + intSeconds + ", intMilliseconds=" + intMilliseconds);
651
652    }
653
654    //drop the extra precision from the milliseconds
655    intHundredths = Math.floor(intMilliseconds / 10);
656
657    //put in padding 0's and concatinate to get the proper format
658    strCMITimeSpan = ZeroPad(intHours, 4) + ":" + ZeroPad(intMinutes, 2) + ":" + ZeroPad(intSeconds, 2);
659
660    if (blnIncludeFraction){
661        strCMITimeSpan += "." + intHundredths;
662    }
663
664    WriteToDebug("strCMITimeSpan=" + strCMITimeSpan);
665
666    //check for case where total milliseconds is greater than max supported by strCMITimeSpan
667    if (intHours > 9999)
668    {
669        strCMITimeSpan = "9999:99:99";
670
671        if (blnIncludeFraction){
672            strCMITimeSpan += ".99";
673        }
674    }
675
676    WriteToDebug("returning " + strCMITimeSpan);
677
678    return strCMITimeSpan;
679
680}
681
682function ConvertMilliSecondsToSCORMTime(intTotalMilliseconds, blnIncludeFraction){
683
684    var intHours;
685    var intMinutes;
686    var intSeconds;
687    var intMilliseconds;
688    var intHundredths;
689    var strCMITimeSpan;
690
691    WriteToDebug("In ConvertMilliSecondsToSCORMTime, intTotalMilliseconds = " + intTotalMilliseconds + ", blnIncludeFraction = " + blnIncludeFraction);
692
693    if (blnIncludeFraction == null || blnIncludeFraction == undefined){
694        blnIncludeFraction = true;
695    }
696
697    //extract time parts
698    intMilliseconds = intTotalMilliseconds % 1000;
699
700    intSeconds = ((intTotalMilliseconds - intMilliseconds) / 1000) % 60;
701
702    intMinutes = ((intTotalMilliseconds - intMilliseconds - (intSeconds * 1000)) / 60000) % 60;
703
704    intHours = (intTotalMilliseconds - intMilliseconds - (intSeconds * 1000) - (intMinutes * 60000)) / 3600000;
705
706    WriteToDebug("Separated Parts, intHours=" + intHours + ", intMinutes=" + intMinutes + ", intSeconds=" + intSeconds + ", intMilliseconds=" + intMilliseconds);
707
708    /*
709    deal with exceptional case when content used a huge amount of time and interpreted CMITimstamp
710    to allow a number of intMinutes and seconds greater than 60 i.e. 9999:99:99.99 instead of 9999:60:60:99
711    note - this case is permissable under SCORM, but will be exceptionally rare
712    */
713
714    if (intHours == 10000)
715    {
716        WriteToDebug("Max intHours detected");
717
718        intHours = 9999;
719
720        intMinutes = (intTotalMilliseconds - (intHours * 3600000)) / 60000;
721        if (intMinutes == 100)
722        {
723            intMinutes = 99;
724        }
725        intMinutes = Math.floor(intMinutes);
726
727        intSeconds = (intTotalMilliseconds - (intHours * 3600000) - (intMinutes * 60000)) / 1000;
728        if (intSeconds == 100)
729        {
730            intSeconds = 99;
731        }
732        intSeconds = Math.floor(intSeconds);
733
734        intMilliseconds = (intTotalMilliseconds - (intHours * 3600000) - (intMinutes * 60000) - (intSeconds * 1000));
735
736        WriteToDebug("Separated Parts, intHours=" + intHours + ", intMinutes=" + intMinutes + ", intSeconds=" + intSeconds + ", intMilliseconds=" + intMilliseconds);
737
738    }
739
740    //drop the extra precision from the milliseconds
741    intHundredths = Math.floor(intMilliseconds / 10);
742
743    //put in padding 0's and concatinate to get the proper format
744    strCMITimeSpan = ZeroPad(intHours, 4) + ":" + ZeroPad(intMinutes, 2) + ":" + ZeroPad(intSeconds, 2);
745
746    if (blnIncludeFraction){
747        strCMITimeSpan += "." + intHundredths;
748    }
749
750    WriteToDebug("strCMITimeSpan=" + strCMITimeSpan);
751
752    //check for case where total milliseconds is greater than max supported by strCMITimeSpan
753    if (intHours > 9999)
754    {
755        strCMITimeSpan = "9999:99:99";
756
757        if (blnIncludeFraction){
758            strCMITimeSpan += ".99";
759        }
760    }
761
762    WriteToDebug("returning " + strCMITimeSpan);
763
764    return strCMITimeSpan;
765
766}
767
768
769// TODO: using years and months in the ISO8601 interval gives varying results
770//       because of the change in number of days in months (and years--leap)
771function ConvertMilliSecondsIntoSCORM2004Time(intTotalMilliseconds){
772
773    WriteToDebug("In ConvertMilliSecondsIntoSCORM2004Time intTotalMilliseconds=" + intTotalMilliseconds);
774
775    var ScormTime = "";
776
777    var HundredthsOfASecond;    //decrementing counter - work at the hundreths of a second level because that is all the precision that is required
778
779    var Seconds;    // 100 hundreths of a seconds
780    var Minutes;    // 60 seconds
781    var Hours;      // 60 minutes
782    var Days;       // 24 hours
783    var Months;     // assumed to be an "average" month (figures a leap year every 4 years) = ((365*4) + 1) / 48 days - 30.4375 days per month
784    var Years;      // assumed to be 12 "average" months
785
786    var HUNDREDTHS_PER_SECOND = 100;
787    var HUNDREDTHS_PER_MINUTE = HUNDREDTHS_PER_SECOND * 60;
788    var HUNDREDTHS_PER_HOUR   = HUNDREDTHS_PER_MINUTE * 60;
789    var HUNDREDTHS_PER_DAY    = HUNDREDTHS_PER_HOUR * 24;
790    var HUNDREDTHS_PER_MONTH  = HUNDREDTHS_PER_DAY * (((365 * 4) + 1) / 48);
791    var HUNDREDTHS_PER_YEAR   = HUNDREDTHS_PER_MONTH * 12;
792
793
794    HundredthsOfASecond = Math.floor(intTotalMilliseconds / 10);
795
796    Years = Math.floor(HundredthsOfASecond / HUNDREDTHS_PER_YEAR);
797    HundredthsOfASecond -= (Years * HUNDREDTHS_PER_YEAR);
798
799    Months = Math.floor(HundredthsOfASecond / HUNDREDTHS_PER_MONTH);
800    HundredthsOfASecond -= (Months * HUNDREDTHS_PER_MONTH);
801
802    Days = Math.floor(HundredthsOfASecond / HUNDREDTHS_PER_DAY);
803    HundredthsOfASecond -= (Days * HUNDREDTHS_PER_DAY);
804
805    Hours = Math.floor(HundredthsOfASecond / HUNDREDTHS_PER_HOUR);
806    HundredthsOfASecond -= (Hours * HUNDREDTHS_PER_HOUR);
807
808    Minutes = Math.floor(HundredthsOfASecond / HUNDREDTHS_PER_MINUTE);
809    HundredthsOfASecond -= (Minutes * HUNDREDTHS_PER_MINUTE);
810
811    Seconds = Math.floor(HundredthsOfASecond / HUNDREDTHS_PER_SECOND);
812    HundredthsOfASecond -= (Seconds * HUNDREDTHS_PER_SECOND);
813
814
815    if (Years > 0) {
816        ScormTime += Years + "Y";
817    }
818    if (Months > 0){
819        ScormTime += Months + "M";
820    }
821    if (Days > 0){
822        ScormTime += Days + "D";
823    }
824
825    //check to see if we have any time before adding the "T"
826    if ((HundredthsOfASecond + Seconds + Minutes + Hours) > 0 ){
827
828        ScormTime += "T";
829
830        if (Hours > 0){
831            ScormTime += Hours + "H";
832        }
833
834        if (Minutes > 0){
835            ScormTime += Minutes + "M";
836        }
837
838        if ((HundredthsOfASecond + Seconds) > 0){
839            ScormTime += Seconds;
840
841            if (HundredthsOfASecond > 0){
842                ScormTime += "." + HundredthsOfASecond;
843            }
844
845            ScormTime += "S";
846        }
847
848    }
849
850
851    if (ScormTime == ""){
852        ScormTime = "T0S";
853    }
854
855    ScormTime = "P" + ScormTime;
856
857    WriteToDebug("Returning-" + ScormTime);
858
859    return ScormTime;
860}
861
862
863
864function ZeroPad(intNum, intNumDigits){
865
866    WriteToDebug("In ZeroPad intNum=" + intNum + " intNumDigits=" + intNumDigits);
867
868    var strTemp;
869    var intLen;
870    var decimalToPad;
871    var i;
872    var isNeg = false;
873    //need to check to see if intNum has +/- at the front
874
875    strTemp = new String(intNum);
876
877    if(strTemp.indexOf('-') != -1){
878        isNeg = true;
879        //remove the '-' for now
880        strTemp = strTemp.substr(1,strTemp.length);
881    }
882
883    //check for .5 (used in TimeZone parsing)
884    if(strTemp.indexOf('.') != -1){
885        strTemp.replace('.','');
886        decimalToPad = strTemp.substr(strTemp.indexOf('.')+1,strTemp.length);
887        strTemp = strTemp.substr(0,strTemp.indexOf('.'));
888    }
889
890    intLen = strTemp.length;
891
892    if (intLen > intNumDigits){
893        WriteToDebug("Length of string is greater than num digits, trimming string");
894        strTemp = strTemp.substr(0,intNumDigits);
895    }
896    else{
897        for (i=intLen; i<intNumDigits; i++){
898            strTemp = "0" + strTemp;
899        }
900    }
901
902    if(isNeg==true){
903        //add the '-' back if needed
904        strTemp = '-'+strTemp;
905    }
906
907    if(decimalToPad!=null && decimalToPad!=''){
908        if(decimalToPad.length==1){
909            strTemp += ':'+decimalToPad+'0';
910        }else{
911            strTemp += ':'+decimalToPad;
912        }
913    }
914
915    WriteToDebug("Returning - " + strTemp);
916    return strTemp;
917}
918
919function IsValidDecimalRange(strValue){
920    WriteToDebug("In IsDecimalRange, strValue=" + strValue);
921
922    var REG_EX_REAL_DELIMITER = "[:]",
923        aryNumericRangeSplit,
924        strMin,
925        strMax;
926
927    strValue = new String(strValue);
928
929    aryNumericRangeSplit = strValue.split(REG_EX_REAL_DELIMITER);
930
931    if (aryNumericRangeSplit.length === 2){
932        //data is in min[:]max notation. Let's check it....
933        strMin = Trim(aryNumericRangeSplit[0]);
934        strMax = Trim(aryNumericRangeSplit[1]);
935
936        //It's valid to have N[:]N, N[:]M, N[:], [:]N, [:] so we only check if the part is a valid decimal if it's been supplied.
937        if (strMin.length > 0 && !IsValidDecimal(strMin))
938        {
939            WriteToDebug("Returning False - min value supplied range is not a valid decimal, min=" + strMin);
940            return false;
941        }
942        if (strMax.length > 0 && !IsValidDecimal(strMax))
943        {
944            WriteToDebug("Returning False - max value supplied for range is not a valid decimal, max=" + strMax);
945            return false;
946        }
947        //min cannot be greater than max. It can be the same however....
948        if (strMin.length > 0 && strMax.length > 0 && parseFloat(strMin) > parseFloat(strMax))
949        {
950            WriteToDebug("Returning False - min value supplied for range is greater than the max, min=" + strMin + ", max=" + strMax);
951            return false;
952        }
953        return true;
954
955    }
956
957    WriteToDebug("Returning false - string supplied for range has incorrect number of parts, parts=" + aryNumericRangeSplit.length + ", strValue=" +strValue);
958    return false;
959}
960
961
962//This function assumes that the strValue parameter that is the numeric range has been previously
963//checked for validity using IsValidDecimalRange() prior to calling this function.
964
965
966//The scenarios for conversion are described below (format is <min>[:]<max>).....
967
968//If the learner's response was correct return their response. This covers scenarios:
969//N[:]N + correct -> learner response
970//N[:]M + correct -> learner response
971//N[:] + correct -> learner response
972//[:]N + correct -> learner response
973//[:] + correct -> learner response
974
975//If <min> is present and the learner was incorrect, then return the min. This covers scenarios:
976//N[:]N + incorrect -> N
977//N[:]M + incorrect -> N
978//N[:] + incorrect -> N
979
980//If <max> is present and learner was incorrect, then return the max. This covers scenarios:
981//[:]N + incorrect -> N
982
983//[:] + incorrect -> this should not be possible as [:] indicates any number is correct and the learner response should
984//have been checked for being a decimal. Return null here. Calling methods should check for null and act accordingly
985function ConvertDecimalRangeToDecimalBasedOnLearnerResponse(strValue, strLearnerResponse, blnCorrect)
986{
987    WriteToDebug("In ConvertDecimalRangeToDecimalBasedOnLearnerResponse strValue=" + strValue + ",strLearnerResponse=" + strLearnerResponse + ",blnCorrect=" + blnCorrect);
988
989    var REG_EX_REAL_DELIMITER = "[:]",
990        aryNumericRangeSplit,
991        strMin,
992        strMax;
993
994    //If it's indicated the learner was correct then just return their answer.
995    if (blnCorrect) {
996        WriteToDebug("Returning strLearnerResponse");
997        return strLearnerResponse;
998    }
999
1000    strValue = new String(strValue);
1001    aryNumericRangeSplit = strValue.split(REG_EX_REAL_DELIMITER);
1002
1003    if (aryNumericRangeSplit.length === 2) {
1004        strMin = Trim(aryNumericRangeSplit[0]);
1005        strMax = Trim(aryNumericRangeSplit[1]);
1006
1007        //if min is present
1008        if (strMin.length > 0) {
1009            WriteToDebug("Returning strMin");
1010            return strMin;
1011        }
1012        //if max is present
1013        else if (strMax.length > 0) {
1014            WriteToDebug("Returning strMax");
1015            return strMax;
1016        }
1017    }
1018
1019    //[:] + incorrect (plus if aryNumberRangeSplit doesn't contain 2 parts,
1020    //which it never should if range is checked for validity prior to this function)
1021    WriteToDebug("Returning null");
1022    return null;
1023}
1024
1025
1026function IsValidDecimal(strValue){
1027
1028    WriteToDebug("In IsValidDecimal, strValue=" + strValue);
1029
1030    strValue = new String(strValue);
1031
1032    //check for characters "0-9", ".", and "-" only
1033    if (strValue.search(/[^.\d-]/) > -1){
1034        WriteToDebug("Returning False - character other than a digit, dash or period found");
1035        return false;
1036    }
1037
1038    //if contains a dash, ensure it is first and that there is only 1
1039    if (strValue.search("-") > -1){
1040        if (strValue.indexOf("-", 1) > -1){
1041            WriteToDebug("Returning False - dash found in the middle of the string");
1042            return false;
1043        }
1044    }
1045
1046    //ensure only 1 decimal point
1047    if (strValue.indexOf(".") != strValue.lastIndexOf(".")){
1048        WriteToDebug("Returning False - more than one decimal point found");
1049        return false;
1050    }
1051
1052    //ensure there is at least 1 digit
1053    if (strValue.search(/\d/) < 0){
1054        WriteToDebug("Returning False - no digits found");
1055        return false;
1056    }
1057
1058    WriteToDebug("Returning True");
1059    return true;
1060
1061}
1062
1063
1064function IsAlphaNumeric(strValue){
1065    WriteToDebug("In IsAlphaNumeric");
1066    if (strValue.search(/\w/) < 0){
1067        WriteToDebug("Returning false");
1068        return false;
1069    }
1070    else{
1071        WriteToDebug("Returning true");
1072        return true;
1073    }
1074}
1075
1076
1077function ReverseNameSequence(strName)
1078{
1079    var strFirstName;
1080    var strLastName;
1081    var intCommaLoc;
1082
1083
1084    //debug
1085    if (strName=="") strName="Not Found, Learner Name";
1086
1087    intCommaLoc = strName.indexOf(",");
1088    strFirstName = strName.slice(intCommaLoc+1);
1089    strLastName = strName.slice(0, intCommaLoc);
1090
1091    strFirstName = Trim(strFirstName);
1092    strLastName = Trim(strLastName);
1093
1094
1095
1096    return strFirstName + ' ' + strLastName;
1097}
1098
1099
1100function LTrim(str) {                   // remove leading spaces
1101    str = new String(str);
1102    return(str.replace(/^\s+/, ''));
1103
1104}
1105
1106function RTrim(str) {                   // remove trailing spaces
1107    str = new String(str);
1108    return(str.replace(/\s+$/, ''));
1109}
1110
1111function Trim(strToTrim) {
1112    var str = LTrim(RTrim(strToTrim)); // remove leading and trailing spaces
1113  return (str.replace(/\s{2,}/g," ")); // replace multiple spaces with single spaces
1114}
1115
1116
1117
1118
1119function GetValueFromDataChunk(strID)
1120{
1121    var strChunk = new String(GetDataChunk());
1122    var aryPairs = new Array();
1123    var aryValues = new Array();
1124    var i;
1125
1126    aryPairs = strChunk.split(parent.DATA_CHUNK_PAIR_SEPARATOR);
1127
1128    for (i=0;i<aryPairs.length;i++)
1129    {
1130        aryValues = aryPairs[i].split(parent.DATA_CHUNK_VALUE_SEPARATOR);
1131        if (aryValues[0]==strID) return aryValues[1];
1132    }
1133
1134    return '';
1135}
1136
1137
1138function SetDataChunkValue(strID, strValue)
1139{
1140    var strChunk = new String(GetDataChunk());
1141    var aryPairs = new Array();
1142    var aryValues = new Array();
1143    var i;
1144    var blnFound = new Boolean(false);
1145
1146
1147
1148    aryPairs = strChunk.split(parent.DATA_CHUNK_PAIR_SEPARATOR);
1149
1150    for (i=0;i<aryPairs.length;i++)
1151    {
1152        aryValues = aryPairs[i].split(parent.DATA_CHUNK_VALUE_SEPARATOR);
1153        if (aryValues[0]==strID)
1154        {
1155            aryValues[1] = strValue;
1156            blnFound = true;
1157            aryPairs[i] = aryValues[0] + parent.DATA_CHUNK_VALUE_SEPARATOR + aryValues[1];
1158        }
1159    }
1160
1161
1162    if (blnFound==true)
1163    {
1164        strChunk = aryPairs.join(parent.DATA_CHUNK_PAIR_SEPARATOR);
1165    }
1166    else
1167    {
1168        if (strChunk=='')
1169        {
1170            //if data chunk is empty, just add this value
1171            strChunk = strID + parent.DATA_CHUNK_VALUE_SEPARATOR + strValue;
1172        }
1173        else
1174        {
1175            //otherwise, append
1176            strChunk += parent.DATA_CHUNK_PAIR_SEPARATOR + strID + parent.DATA_CHUNK_VALUE_SEPARATOR + strValue;
1177        }
1178    }
1179
1180    SetDataChunk(strChunk);
1181
1182    return true;
1183}
1184
1185
1186function GetLastDirAndPageName(str)
1187{
1188    var page = new String(str);
1189    var LastSlashLocation = page.lastIndexOf("/");
1190    var SecondLastSlashLocation = page.lastIndexOf("/", LastSlashLocation-1);
1191    return page.substr(SecondLastSlashLocation+1);
1192
1193}
1194
1195
1196function RoundToPrecision(number, significantDigits){
1197
1198    number = parseFloat(number);
1199    return (Math.round(number * Math.pow(10, significantDigits)) / Math.pow(10, significantDigits))
1200}
1201
1202function IsAbsoluteUrl(urlStr) {
1203    return urlStr != null && (urlStr.indexOf("http://") == 0 || urlStr.indexOf("https://") == 0)
1204}
1205
1206function TouchCloud() {
1207    if(APPID != null && APPID != "" && APPID != "__APPID__" && CLOUDURL !== null && CLOUDURL.indexOf("http") === 0){
1208        var cloudForm=document.createElement("form");
1209		cloudForm.name = "cloudform";
1210		cloudForm.id = "cloudform";
1211		cloudForm.style = "display:none;";
1212
1213		document.body.appendChild(cloudForm);
1214
1215        var elAppId = document.createElement("input");
1216        elAppId.name = "appId";
1217        elAppId.value = APPID;
1218        elAppId.type = "hidden";
1219        cloudForm.appendChild(elAppId);
1220
1221        var elUrl = document.createElement("input");
1222        elUrl.name = "servingUrl";
1223        elUrl.type = "hidden";
1224        elUrl.value = document.location.href;
1225        cloudForm.appendChild(elUrl);
1226
1227        var elVersion = document.createElement("input");
1228        elVersion.name = "version";
1229        elVersion.type = "hidden";
1230        elVersion.value = VERSION;
1231        cloudForm.appendChild(elVersion);
1232
1233        cloudForm.target = "rusticisoftware_aicc_results";      //keep this name fairly unique to avoid a potential naming conflict with LMS frames
1234        cloudForm.action = CLOUDURL;
1235        document.getElementById('cloudform').submit();
1236		return true;
1237
1238    }else{
1239        return false;
1240    }
1241}
1242
1243function IsNumeric(n) {
1244	return !isNaN(parseFloat(n)) && isFinite(n);
1245}
1246
1247function loadScript(url, callback) {
1248    // add the script tag to the head
1249    var head = document.getElementsByTagName('head')[0],
1250        script = document.createElement('script');
1251    script.type = 'text/javascript';
1252    script.src = url;
1253
1254    //
1255    // this is a beast of browser compatibility woes, so some of this
1256    // is gleaned from looking at the source for YUI's Y.Get.Transaction
1257    // and based on IE < 9 not handling .onload as seen here:
1258    //
1259    // http://msdn.microsoft.com/en-us/library/ie/hh180173(v=vs.85).aspx
1260    //
1261    // and based on this table:
1262    //
1263    // http://pieisgood.org/test/script-link-events/
1264    //
1265    // which was generated when creating Y.Get for YUI and linked from MDN here:
1266    //
1267    // https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script#See_also
1268    //
1269    if (! script.addEventListener || (document.documentMode && document.documentMode < 9)) {
1270        script.onreadystatechange = function () {
1271            if (/loaded|complete/.test(script.readyState)) {
1272                script.onreadystatechange = null;
1273                callback();
1274            }
1275        };
1276    }
1277    else {
1278        script.addEventListener("load", callback, false);
1279    }
1280
1281    // fire the loading
1282    head.appendChild(script);
1283}
1284var STANDARD = 'SCORM2004';
1285
1286var SCORM2004_LOGOUT = "logout";
1287var SCORM2004_SUSPEND = "suspend";
1288var SCORM2004_NORMAL_EXIT = "normal";
1289var SCORM2004_TIMEOUT = "time-out";
1290
1291var SCORM2004_PASSED = "passed";
1292var SCORM2004_FAILED = "failed";
1293var SCORM2004_UNKNOWN = "unknown";
1294
1295var SCORM2004_COMPLETED = "completed";
1296var SCORM2004_INCOMPLETE = "incomplete";
1297var SCORM2004_NOT_ATTEMPTED = "not attempted";
1298
1299//var SCORM2004_BROWSED = "browsed";
1300
1301var SCORM2004_CREDIT = "credit";
1302var SCORM2004_NO_CREDIT = "no-credit";
1303
1304var SCORM2004_BROWSE = "browse";
1305var SCORM2004_NORMAL = "normal";
1306var SCORM2004_REVIEW = "review";
1307
1308var SCORM2004_ENTRY_ABINITIO = "ab-initio";
1309var SCORM2004_ENTRY_RESUME = "resume";
1310var SCORM2004_ENTRY_NORMAL = "";
1311
1312var SCORM2004_TLA_EXIT_MESSAGE = "exit,message";
1313var SCORM2004_TLA_EXIT_NO_MESSAGE = "exit,no message";
1314var SCORM2004_TLA_CONTINUE_MESSAGE = "continue,message";
1315var SCORM2004_TLA_CONTINUE_NO_MESSAGE = "continue,no message";
1316
1317
1318var SCORM2004_RESULT_CORRECT = "correct";
1319var SCORM2004_RESULT_WRONG = "incorrect";
1320var SCORM2004_RESULT_UNANTICIPATED = "unanticipated";
1321var SCORM2004_RESULT_NEUTRAL = "neutral";
1322
1323var SCORM2004_INTERACTION_TYPE_TRUE_FALSE = "true-false";
1324var SCORM2004_INTERACTION_TYPE_CHOICE = "choice";
1325var SCORM2004_INTERACTION_TYPE_FILL_IN = "fill-in";
1326var SCORM2004_INTERACTION_TYPE_LONG_FILL_IN = "long-fill-in";
1327var SCORM2004_INTERACTION_TYPE_MATCHING = "matching";
1328var SCORM2004_INTERACTION_TYPE_PERFORMANCE = "performance";
1329var SCORM2004_INTERACTION_TYPE_SEQUENCING = "sequencing";
1330var SCORM2004_INTERACTION_TYPE_LIKERT = "likert";
1331var SCORM2004_INTERACTION_TYPE_NUMERIC = "numeric";
1332
1333
1334var SCORM2004_NO_ERROR = "0";
1335var SCORM2004_ERROR_INVALID_PREFERENCE = "-1";
1336var SCORM2004_ERROR_INVALID_STATUS = "-2";
1337var SCORM2004_ERROR_INVALID_SPEED = "-3";
1338var SCORM2004_ERROR_INVALID_TIMESPAN = "-4";
1339var SCORM2004_ERROR_INVALID_TIME_LIMIT_ACTION = "-5";
1340var SCORM2004_ERROR_INVALID_DECIMAL = "-6";
1341var SCORM2004_ERROR_INVALID_CREDIT = "-7";
1342var SCORM2004_ERROR_INVALID_LESSON_MODE = "-8";
1343var SCORM2004_ERROR_INVALID_ENTRY = "-9";
1344
1345var SCORM2004_TRUE = "true";
1346var SCORM2004_FALSE = "false";
1347
1348var SCORM2004_EARLIEST_DATE = new Date("1/1/1900");
1349
1350
1351
1352
1353var intSCORM2004Error = SCORM2004_NO_ERROR;
1354var strSCORM2004ErrorString = "";
1355var strSCORM2004ErrorDiagnostic = "";
1356
1357var SCORM2004_objAPI = null;
1358
1359var blnReviewModeSoReadOnly = false;
1360
1361var blnSCORM2004_SSP_Is_Supported = null;
1362
1363function SCORM2004_Initialize(){
1364
1365    WriteToDebug("In SCORM2004_Initialize");
1366
1367    var blnResult = true;
1368
1369    SCORM2004_ClearErrorInfo();
1370
1371    WriteToDebug("Grabbing API");
1372
1373    try{
1374        SCORM2004_objAPI = SCORM2004_GrabAPI();
1375    }
1376    catch (e){
1377        WriteToDebug("Error grabbing 1.2 API-" + e.name + ":" + e.message);
1378    }
1379
1380    if (typeof(SCORM2004_objAPI) == "undefined" || SCORM2004_objAPI == null){
1381        WriteToDebug("Unable to acquire SCORM API:")
1382        WriteToDebug("SCORM2004_objAPI=" + typeof(SCORM2004_objAPI));
1383
1384        InitializeExecuted(false, "Error - unable to acquire LMS API, content may not play properly and results may not be recorded.  Please contact technical support.");
1385        return false;
1386    }
1387
1388    WriteToDebug("Calling LMSInit");
1389
1390    blnResult = SCORM2004_CallInitialize();
1391
1392    if (! blnResult){
1393        WriteToDebug("ERROR Initializing LMS");
1394
1395        InitializeExecuted(false, "Error initializing communications with LMS");
1396
1397        return false;
1398    }
1399
1400
1401
1402    if (! PREVENT_STATUS_CHANGE_DURING_INIT) {
1403        //only set the status to incomplete if it's not attempted yet
1404        if (SCORM2004_GetStatus() == LESSON_STATUS_NOT_ATTEMPTED){
1405            WriteToDebug("Setting Status to Incomplete");
1406            blnResult = SCORM2004_CallSetValue("cmi.completion_status", SCORM2004_INCOMPLETE);
1407        }
1408    }
1409
1410
1411    //we want to set the exit type to suspend immediately because some LMS's only store data if they get a suspend request
1412    blnResult = SCORM2004_CallSetValue("cmi.exit", SCORM2004_TranslateExitTypeToSCORM(DEFAULT_EXIT_TYPE)) && blnResult;
1413
1414    //if mode is review, check if we should go to read only mode
1415    if (SCORM2004_GetLessonMode() == MODE_REVIEW){
1416        if (!(typeof(REVIEW_MODE_IS_READ_ONLY) == "undefined") && REVIEW_MODE_IS_READ_ONLY === true){
1417            blnReviewModeSoReadOnly = true;
1418        }
1419    }
1420
1421    WriteToDebug("Calling InitializeExecuted with parameter-" + blnResult);
1422
1423    InitializeExecuted(blnResult, "");
1424
1425    return;
1426
1427}
1428
1429
1430
1431
1432function SCORM2004_Finish(strExitType, blnStatusWasSet){
1433
1434    WriteToDebug("In SCORM2004_Finish strExitType=" + strExitType + ", blnStatusWasSet=" + blnStatusWasSet);
1435
1436    var strStatusAfterCompletion;
1437    var blnResult = true;
1438
1439    SCORM2004_ClearErrorInfo();
1440
1441    if ( (strExitType == EXIT_TYPE_FINISH) && ! blnStatusWasSet ){
1442
1443        WriteToDebug("Getting completion status");
1444
1445        strStatusAfterCompletion = SCORM2004_GetCompletionStatus();
1446
1447        WriteToDebug("Setting completion status to " + strStatusAfterCompletion);
1448
1449        blnResult = SCORM2004_CallSetValue("cmi.completion_status", strStatusAfterCompletion) && blnResult;
1450
1451    }
1452
1453    if( strExitType == EXIT_TYPE_SUSPEND && USE_2004_SUSPENDALL_NAVREQ ) {
1454        WriteToDebug("Setting adl.nav.request to suspendAll");
1455        blnResult = SCORM2004_CallSetValue("adl.nav.request","suspendAll");
1456    }
1457
1458    WriteToDebug("Setting Exit");
1459
1460    blnResult = SCORM2004_CallSetValue("cmi.exit", SCORM2004_TranslateExitTypeToSCORM(strExitType)) && blnResult;
1461
1462    WriteToDebug("Calling Commit");
1463
1464    blnResult = SCORM2004_CallCommit() && blnResult;
1465
1466    WriteToDebug("Calling Finish");
1467
1468    blnResult = SCORM2004_CallTerminate() && blnResult;
1469
1470    WriteToDebug("Returning " + blnResult);
1471
1472    return blnResult;
1473
1474}
1475
1476
1477function SCORM2004_CommitData(){
1478
1479    WriteToDebug("In SCORM2004_CommitData");
1480    SCORM2004_ClearErrorInfo();
1481    return SCORM2004_CallCommit();
1482
1483}
1484
1485
1486
1487//---------------------------------------------------------------------------------
1488//General Get and Set Values
1489
1490function SCORM2004_GetStudentID(){
1491
1492    WriteToDebug("In SCORM2004_GetStudentID");
1493    SCORM2004_ClearErrorInfo();
1494    return SCORM2004_CallGetValue("cmi.learner_id");
1495
1496}
1497
1498function SCORM2004_GetStudentName(){
1499
1500    WriteToDebug("In SCORM2004_GetStudentName");
1501    SCORM2004_ClearErrorInfo();
1502    return SCORM2004_CallGetValue("cmi.learner_name");
1503
1504}
1505
1506function SCORM2004_GetBookmark(){
1507    WriteToDebug("In SCORM2004_GetBookmark");
1508    SCORM2004_ClearErrorInfo();
1509    return SCORM2004_CallGetValue("cmi.location");
1510
1511}
1512
1513function SCORM2004_SetBookmark(strBookmark){
1514    WriteToDebug("In SCORM2004_SetBookmark strBookmark=" + strBookmark);
1515    SCORM2004_ClearErrorInfo();
1516    return SCORM2004_CallSetValue("cmi.location", strBookmark);
1517}
1518
1519function SCORM2004_GetDataChunk(){
1520    WriteToDebug("In SCORM2004_GetDataChunk");
1521    SCORM2004_ClearErrorInfo();
1522    return SCORM2004_CallGetValue("cmi.suspend_data");
1523}
1524
1525function SCORM2004_SetDataChunk(strData){
1526    // 4000 characters for 2nd edition
1527    // 64000 characters here for 3rd edition
1528    WriteToDebug("In SCORM2004_SetDataChunk");
1529    SCORM2004_ClearErrorInfo();
1530
1531    if(USE_STRICT_SUSPEND_DATA_LIMITS==true) {
1532        if(strData.length > 4000) {
1533            WriteToDebug("SCORM2004_SetDataChunk - suspend_data too large for SCORM 2004 2nd ed (4000 character limit) but will try to persist anyway.");
1534            if(strData.length > 64000) {
1535                WriteToDebug("SCORM2004_SetDataChunk - suspend_data too large for SCORM 2004 3rd & 4th ed (64000 character limit) so failing to persist.");
1536                return false;
1537            }else{
1538                //value is between 4000 and 64000 so we can attempt to persist
1539                return SCORM2004_CallSetValue("cmi.suspend_data", strData);
1540            }
1541        }else{
1542            //length is less than 4000 so it should be fine for all standards
1543            return SCORM2004_CallSetValue("cmi.suspend_data", strData);
1544        }
1545    }else{
1546        //don't check any lengths and just send it to the API
1547        return SCORM2004_CallSetValue("cmi.suspend_data", strData);
1548    }
1549
1550}
1551
1552function SCORM2004_GetLaunchData(){
1553    WriteToDebug("In SCORM2004_GetLaunchData");
1554    SCORM2004_ClearErrorInfo();
1555    return SCORM2004_CallGetValue("cmi.launch_data");
1556}
1557
1558function SCORM2004_GetComments(){
1559
1560    WriteToDebug("In SCORM2004_GetComments");
1561
1562    SCORM2004_ClearErrorInfo();
1563
1564    var intCommentCount;
1565    var strComments = "";
1566
1567    intCommentCount = SCORM2004_CallGetValue("cmi.comments_from_learner._count");
1568
1569    for (var i=0; i < intCommentCount; i++){
1570
1571        if (strComments.length > 0){
1572            strComments += " | ";
1573        }
1574
1575        strComments += SCORM2004_CallGetValue("cmi.comments_from_learner." + i + ".comment");
1576    }
1577
1578    return strComments;
1579
1580}
1581
1582function SCORM2004_WriteComment(strComment){
1583
1584    WriteToDebug("In SCORM2004_WriteComment strComment=" + strComment);
1585
1586    var intCurrentIndex;
1587    var blnResult;
1588
1589    SCORM2004_ClearErrorInfo();
1590
1591    //remove the "|" since we can use the comments collection
1592    if (strComment.search(/ \| /) == 0){
1593        strComment = strComment.substr(3);
1594    }
1595
1596    //remove encoding of "|"
1597    strComment.replace(/\|\|/g, "|")
1598
1599    intCurrentIndex = SCORM2004_CallGetValue("cmi.comments_from_learner._count");
1600
1601    blnResult = SCORM2004_CallSetValue("cmi.comments_from_learner." + intCurrentIndex + ".comment", strComment);
1602    blnResult = SCORM2004_CallSetValue("cmi.comments_from_learner." + intCurrentIndex + ".timestamp", ConvertDateToIso8601TimeStamp(new Date())) && blnResult;
1603
1604    return blnResult;
1605}
1606
1607
1608function SCORM2004_GetLMSComments(){
1609
1610    WriteToDebug("In SCORM2004_GetLMSComments");
1611
1612    SCORM2004_ClearErrorInfo();
1613
1614    var intCommentCount;
1615    var strComments = "";
1616
1617    intCommentCount = SCORM2004_CallGetValue("cmi.comments_from_lms._count");
1618
1619    for (var i=0; i < intCommentCount; i++){
1620
1621        if (strComments.length > 0){
1622            strComments += " \r\n";
1623        }
1624
1625        strComments += SCORM2004_CallGetValue("cmi.comments_from_lms." + i + ".comment");
1626    }
1627
1628    return strComments;
1629}
1630
1631
1632function SCORM2004_GetAudioPlayPreference(){
1633
1634    var intTempPreference;
1635
1636    WriteToDebug("In SCORM2004_GetAudioPlayPreference");
1637
1638    SCORM2004_ClearErrorInfo();
1639
1640    intTempPreference = SCORM2004_CallGetValue("cmi.learner_preference.audio_level");
1641
1642    if (intTempPreference == ""){
1643        intTempPreference = 0;
1644    }
1645
1646    intTempPreference = parseInt(intTempPreference, 10);
1647
1648    WriteToDebug("intTempPreference=" + intTempPreference);
1649
1650    if (intTempPreference > 0){
1651        WriteToDebug("Returning On");
1652        return PREFERENCE_ON;
1653    }
1654    else if (intTempPreference <= 0) {
1655        WriteToDebug("Returning Off");
1656        return PREFERENCE_OFF;
1657    }
1658    else{
1659        WriteToDebug("Error: Invalid preference");
1660        SCORM2004_SetErrorInfoManually(SCORM2004_ERROR_INVALID_PREFERENCE,
1661                                  "Invalid audio preference received from LMS",
1662                                  "intTempPreference=" + intTempPreference);
1663        return null;
1664    }
1665}
1666
1667
1668
1669function SCORM2004_GetAudioVolumePreference(){
1670
1671    var intTempPreference;
1672
1673    WriteToDebug("In SCORM2004_GetAudioVollumePreference");
1674
1675    SCORM2004_ClearErrorInfo();
1676
1677    intTempPreference = SCORM2004_CallGetValue("cmi.learner_preference.audio_level");
1678
1679    WriteToDebug("intTempPreference=" + intTempPreference);
1680
1681    if (intTempPreference == "") {
1682        intTempPreference = 100;
1683    }
1684
1685    intTempPreference = parseInt(intTempPreference, 10);
1686
1687    if (intTempPreference <= 0){
1688        WriteToDebug("Setting to 100");
1689        intTempPreference = 100;
1690    }
1691
1692    if (! (intTempPreference > 0 && intTempPreference <= 100)){
1693        WriteToDebug("ERROR: invalid preference");
1694        SCORM2004_SetErrorInfoManually(SCORM2004_ERROR_INVALID_PREFERENCE,
1695                                  "Invalid audio preference received from LMS",
1696                                  "intTempPreference=" + intTempPreference);
1697        return null;
1698    }
1699
1700    WriteToDebug("Returning " + intTempPreference);
1701    return intTempPreference;
1702
1703}
1704
1705function SCORM2004_SetAudioPreference(PlayPreference, intPercentOfMaxVolume){
1706
1707    WriteToDebug("In SCORM2004_SetAudioPreference PlayPreference=" + PlayPreference + ", intPercentOfMaxVolume=" + intPercentOfMaxVolume);
1708
1709    SCORM2004_ClearErrorInfo();
1710
1711    if (PlayPreference == PREFERENCE_OFF){
1712        WriteToDebug("Setting percent to 0");
1713        intPercentOfMaxVolume = 0;
1714    }
1715
1716    return SCORM2004_CallSetValue("cmi.learner_preference.audio_level", intPercentOfMaxVolume);
1717}
1718
1719
1720
1721function SCORM2004_SetLanguagePreference(strLanguage){
1722    WriteToDebug("In SCORM2004_SetLanguagePreference strLanguage=" + strLanguage);
1723    SCORM2004_ClearErrorInfo();
1724    return SCORM2004_CallSetValue("cmi.learner_preference.language", strLanguage);
1725
1726}
1727
1728
1729function SCORM2004_GetLanguagePreference(){
1730    WriteToDebug("In SCORM2004_GetLanguagePreference");
1731    SCORM2004_ClearErrorInfo();
1732    return SCORM2004_CallGetValue("cmi.learner_preference.language");
1733
1734}
1735
1736
1737function SCORM2004_SetSpeedPreference(intPercentOfMax){
1738
1739    //SCORM 2004's scale is greater than 0, our range is 1 to 100, just store our range
1740
1741    WriteToDebug("In SCORM2004_SetSpeedPreference intPercentOfMax=" + intPercentOfMax);
1742
1743    SCORM2004_ClearErrorInfo();
1744
1745    return SCORM2004_CallSetValue("cmi.learner_preference.delivery_speed", intPercentOfMax);
1746
1747}
1748
1749
1750function SCORM2004_GetSpeedPreference(){
1751
1752    var intSCORMSpeed;
1753    var intPercentOfMax;
1754
1755    WriteToDebug("In SCORM2004_GetSpeedPreference");
1756
1757    SCORM2004_ClearErrorInfo();
1758
1759    intSCORMSpeed = SCORM2004_CallGetValue("cmi.learner_preference.delivery_speed");
1760
1761    WriteToDebug("intSCORMSpeed=" + intSCORMSpeed);
1762
1763    if (intSCORMSpeed == ""){
1764        WriteToDebug("Detected empty string, defaulting to 100");
1765        intSCORMSpeed = 100;
1766    }
1767
1768    intSCORMSpeed = parseInt(intSCORMSpeed, 10);
1769
1770    if (intSCORMSpeed < 0){
1771        WriteToDebug("ERROR - out of range");
1772        SCORM2004_SetErrorInfoManually(SCORM2004_ERROR_INVALID_SPEED,
1773                                  "Invalid speed preference received from LMS - out of range",
1774                                  "intSCORMSpeed=" + intSCORMSpeed);
1775        return null;
1776    }
1777
1778    WriteToDebug("intSCORMSpeed " + intSCORMSpeed);
1779
1780    return intSCORMSpeed;
1781
1782}
1783
1784
1785
1786
1787function SCORM2004_SetTextPreference(intPreference){
1788
1789    WriteToDebug("In SCORM2004_SetTextPreference intPreference=" + intPreference);
1790
1791    SCORM2004_ClearErrorInfo();
1792
1793    return SCORM2004_CallSetValue("cmi.learner_preference.audio_captioning", intPreference);
1794
1795}
1796
1797
1798function SCORM2004_GetTextPreference(){
1799
1800    var intTempPreference;
1801
1802    WriteToDebug("In SCORM2004_GetTextPreference");
1803
1804    SCORM2004_ClearErrorInfo();
1805
1806    intTempPreference = SCORM2004_CallGetValue("cmi.learner_preference.audio_captioning");
1807    intTempPreference = parseInt(intTempPreference, 10);
1808
1809    WriteToDebug("intTempPreference=" + intTempPreference);
1810
1811    if (intTempPreference > 0){
1812        WriteToDebug("Returning On");
1813        return PREFERENCE_ON;
1814    }
1815    else if (intTempPreference == 0 || intTempPreference == ""){
1816        WriteToDebug("Returning Default");
1817        return PREFERENCE_DEFAULT;
1818    }
1819    else if (intTempPreference < 0) {
1820        WriteToDebug("Returning Off");
1821        return PREFERENCE_OFF;
1822    }
1823    else{
1824        WriteToDebug("Error: Invalid preference");
1825        SCORM2004_SetErrorInfoManually(SCORM2004_ERROR_INVALID_PREFERENCE,
1826                                  "Invalid text preference received from LMS",
1827                                  "intTempPreference=" + intTempPreference);
1828        return null;
1829    }
1830}
1831
1832
1833
1834//---------------------------------------------------------------------------------
1835//Time Management Functions
1836function SCORM2004_GetPreviouslyAccumulatedTime(){
1837
1838    var strIso8601Time;
1839    var intMilliseconds;
1840
1841    WriteToDebug("In SCORM2004_GetPreviouslyAccumulatedTime");
1842
1843
1844    SCORM2004_ClearErrorInfo();
1845
1846    strIso8601Time = SCORM2004_CallGetValue("cmi.total_time")
1847
1848    WriteToDebug("strIso8601Time=" + strIso8601Time);
1849
1850    if (! IsValidIso8601TimeSpan(strIso8601Time)){
1851        WriteToDebug("ERROR - Invalid Iso8601Time");
1852        SCORM2004_SetErrorInfoManually(SCORM2004_ERROR_INVALID_TIMESPAN,
1853                                    "Invalid timespan received from LMS",
1854                                    "strTime=" + strIso8601Time);
1855        return null;
1856    }
1857
1858    intMilliseconds = ConvertScorm2004TimeToMS(strIso8601Time);
1859
1860    WriteToDebug("Returning " + intMilliseconds);
1861
1862    return intMilliseconds;
1863
1864}
1865
1866
1867function SCORM2004_SaveTime(intMilliSeconds){
1868
1869    var strISO8601Time;
1870
1871    WriteToDebug("In SCORM2004_SaveTime intMilliSeconds=" + intMilliSeconds);
1872
1873    SCORM2004_ClearErrorInfo();
1874
1875    strISO8601Time = ConvertMilliSecondsIntoSCORM2004Time(intMilliSeconds);
1876
1877    WriteToDebug("strISO8601Time=" + strISO8601Time);
1878
1879    return SCORM2004_CallSetValue("cmi.session_time", strISO8601Time);
1880}
1881
1882function SCORM2004_GetMaxTimeAllowed(){
1883
1884    var strIso8601Time;
1885    var intMilliseconds;
1886
1887    WriteToDebug("In SCORM2004_GetMaxTimeAllowed");
1888
1889
1890    SCORM2004_ClearErrorInfo();
1891
1892    strIso8601Time = SCORM2004_CallGetValue("cmi.max_time_allowed")
1893
1894    WriteToDebug("strIso8601Time=" + strIso8601Time);
1895
1896    if (strIso8601Time == ""){
1897        strIso8601Time = "20Y";
1898    }
1899
1900    if (! IsValidIso8601TimeSpan(strIso8601Time)){
1901        WriteToDebug("ERROR - Invalid Iso8601Time");
1902        SCORM2004_SetErrorInfoManually(SCORM_ERROR_INVALID_TIMESPAN,
1903                                    "Invalid timespan received from LMS",
1904                                    "strIso8601Time=" + strIso8601Time);
1905        return null;
1906    }
1907
1908    intMilliseconds = ConvertScorm2004TimeToMS(ConvertScorm2004TimeToMS);
1909
1910    WriteToDebug("intMilliseconds=" + intMilliseconds);
1911
1912    return intMilliseconds;
1913
1914}
1915
1916
1917
1918function SCORM2004_DisplayMessageOnTimeout(){
1919
1920    var strTLA;
1921    WriteToDebug("In SCORM2004_DisplayMessageOnTimeout");
1922
1923    SCORM2004_ClearErrorInfo();
1924
1925
1926    strTLA = SCORM2004_CallGetValue("cmi.time_limit_action");
1927
1928    WriteToDebug("strTLA=" + strTLA);
1929
1930    if (strTLA == SCORM2004_TLA_EXIT_MESSAGE || strTLA == SCORM2004_TLA_CONTINUE_MESSAGE){
1931        WriteToDebug("returning true");
1932        return true;
1933    }
1934    else if(strTLA == SCORM2004_TLA_EXIT_NO_MESSAGE || strTLA == SCORM2004_TLA_CONTINUE_NO_MESSAGE || strTLA == ""){
1935        WriteToDebug("returning false");
1936        return false;
1937    }
1938    else{
1939        WriteToDebug("Error invalid TLA");
1940        SCORM2004_SetErrorInfoManually(SCORM2004_ERROR_INVALID_TIME_LIMIT_ACTION,
1941                                  "Invalid time limit action received from LMS",
1942                                  "strTLA=" + strTLA);
1943
1944        return null;
1945    }
1946
1947}
1948
1949function SCORM2004_ExitOnTimeout(){
1950
1951    var strTLA;
1952
1953    WriteToDebug("In SCORM2004_ExitOnTimeout");
1954
1955    SCORM2004_ClearErrorInfo();
1956
1957    strTLA = SCORM2004_CallGetValue("cmi.time_limit_action");
1958
1959    WriteToDebug("strTLA=" + strTLA);
1960
1961    if (strTLA == SCORM2004_TLA_EXIT_MESSAGE || strTLA == SCORM2004_TLA_EXIT_NO_MESSAGE){
1962        WriteToDebug("returning true");
1963        return true;
1964    }
1965    else if(strTLA == SCORM2004_TLA_CONTINUE_MESSAGE || strTLA == SCORM2004_TLA_CONTINUE_NO_MESSAGE || strTLA == ""){
1966        WriteToDebug("returning false");
1967        return false;
1968    }
1969    else{
1970        WriteToDebug("ERROR invalid TLA");
1971        SCORM2004_SetErrorInfoManually(SCORM2004_ERROR_INVALID_TIME_LIMIT_ACTION,
1972                                  "Invalid time limit action received from LMS",
1973                                  "strTLA=" + strTLA);
1974
1975        return null;
1976    }
1977
1978}
1979
1980
1981function SCORM2004_GetPassingScore(){
1982    var fltScore;
1983
1984    WriteToDebug("In SCORM2004_GetPassingScore");
1985
1986    SCORM2004_ClearErrorInfo();
1987
1988    fltScore = SCORM2004_CallGetValue("cmi.scaled_passing_score")
1989
1990    WriteToDebug("fltScore=" + fltScore);
1991
1992    if (fltScore == ""){
1993        fltScore = 0;
1994    }
1995
1996    if ( ! IsValidDecimal(fltScore)){
1997        WriteToDebug("Error - score is not a valid decimal");
1998        SCORM2004_SetErrorInfoManually(SCORM2004_ERROR_INVALID_DECIMAL,
1999                                  "Invalid mastery score received from LMS",
2000                                  "fltScore=" + fltScore);
2001        return null;
2002    }
2003
2004    fltScore = parseFloat(fltScore);
2005
2006    //multiply by 100 to normalize from -1 to 1 to 0-100 - (we always set the score so we know it won't be negative)
2007    fltScore = fltScore * 100;
2008
2009    WriteToDebug("returning fltScore-" + fltScore);
2010
2011    return fltScore;
2012}
2013
2014
2015function SCORM2004_SetScore(intScore, intMaxScore, intMinScore){
2016
2017    var blnResult;
2018    var fltNormalizedScore;
2019
2020    intScore = RoundToPrecision(intScore, 7);
2021    intMaxScore = RoundToPrecision(intMaxScore, 7);
2022    intMinScore = RoundToPrecision(intMinScore, 7);
2023
2024    WriteToDebug("In SCORM2004_SetScore intScore=" + intScore + ", intMaxScore=" + intMaxScore + ", intMinScore=" + intMinScore);
2025
2026    SCORM2004_ClearErrorInfo();
2027
2028    fltNormalizedScore = RoundToPrecision(intScore / 100, 7);
2029
2030    blnResult = SCORM2004_CallSetValue("cmi.score.raw", intScore);
2031    blnResult = SCORM2004_CallSetValue("cmi.score.max", intMaxScore) && blnResult;
2032    blnResult = SCORM2004_CallSetValue("cmi.score.min", intMinScore) && blnResult;
2033
2034    blnResult = SCORM2004_CallSetValue("cmi.score.scaled", fltNormalizedScore) && blnResult;
2035
2036    WriteToDebug("Returning " + blnResult);
2037
2038    return blnResult;
2039}
2040
2041function SCORM2004_GetScore(){
2042
2043
2044    WriteToDebug("In SCORM2004_GetScore");
2045
2046    SCORM2004_ClearErrorInfo();
2047
2048    return SCORM2004_CallGetValue("cmi.score.raw");
2049
2050}
2051
2052function SCORM2004_GetScaledScore(){
2053
2054
2055    WriteToDebug("In SCORM2004_GetScaledScore");
2056
2057    SCORM2004_ClearErrorInfo();
2058
2059    return SCORM2004_CallGetValue("cmi.score.scaled");
2060
2061}
2062
2063function SCORM2004_RecordInteraction(strID, strResponse, blnCorrect, strCorrectResponse, strDescription, intWeighting, intLatency, strLearningObjectiveID, dtmTime, SCORM2004InteractionType){
2064
2065    var blnResult;
2066    var intInteractionIndex;
2067    var strResult;
2068
2069    if (!IsNumeric(blnCorrect)) {
2070	    blnCorrect = new String(blnCorrect);  //have to cast as string to support false
2071	}
2072
2073    SCORM2004_ClearErrorInfo();
2074
2075    intInteractionIndex = SCORM2004_CallGetValue("cmi.interactions._count");
2076
2077    WriteToDebug("intInteractionIndex=" + intInteractionIndex);
2078
2079    if (intInteractionIndex == ""){
2080        WriteToDebug("Setting Interaction Index to 0");
2081        intInteractionIndex = 0;
2082    }
2083
2084    //need to leave support for blnCorrect=t/f for legacy implementations of RSECA
2085    if (blnCorrect == true || blnCorrect =="true" || blnCorrect == INTERACTION_RESULT_CORRECT){
2086        strResult = SCORM2004_RESULT_CORRECT;
2087    }
2088    else if (String(blnCorrect) == "false" || blnCorrect == INTERACTION_RESULT_WRONG){      //compare against the string "false" because ("" == false) evaluates to true
2089        strResult = SCORM2004_RESULT_WRONG;
2090    }
2091    else if (blnCorrect == INTERACTION_RESULT_UNANTICIPATED){
2092        strResult = SCORM2004_RESULT_UNANTICIPATED;
2093    }
2094    else if (blnCorrect == INTERACTION_RESULT_NEUTRAL){
2095        strResult = SCORM2004_RESULT_NEUTRAL;
2096    }
2097    else if (IsNumeric(blnCorrect)) {
2098    	strResult = blnCorrect;
2099    }
2100    else{
2101        strResult = "";
2102    }
2103
2104    WriteToDebug("strResult=" + strResult);
2105
2106    strID = CreateValidIdentifier(strID);
2107
2108    blnResult = SCORM2004_CallSetValue("cmi.interactions." + intInteractionIndex + ".id", strID);
2109    blnResult = SCORM2004_CallSetValue("cmi.interactions." + intInteractionIndex + ".type", SCORM2004InteractionType) && blnResult;
2110
2111    if (strResponse !== null) {
2112        blnResult = SCORM2004_CallSetValue("cmi.interactions." + intInteractionIndex + ".learner_response", strResponse) && blnResult;
2113    }
2114
2115    if (strResult != undefined && strResult != null && strResult != ""){
2116        blnResult = SCORM2004_CallSetValue("cmi.interactions." + intInteractionIndex + ".result", strResult) && blnResult;
2117    }
2118
2119    if (strCorrectResponse != undefined && strCorrectResponse != null && strCorrectResponse != ""){
2120        blnResult = SCORM2004_CallSetValue("cmi.interactions." + intInteractionIndex + ".correct_responses.0.pattern", strCorrectResponse) && blnResult;
2121    }
2122
2123    if (strDescription != undefined && strDescription != null && strDescription != ""){
2124        blnResult = SCORM2004_CallSetValue("cmi.interactions." + intInteractionIndex + ".description", strDescription) && blnResult;
2125    }
2126
2127    if (intWeighting != undefined && intWeighting != null && intWeighting != ""){
2128        blnResult = SCORM2004_CallSetValue("cmi.interactions." + intInteractionIndex + ".weighting", intWeighting) && blnResult;
2129    }
2130
2131    if (intLatency != undefined && intLatency != null && intLatency != ""){
2132        blnResult = SCORM2004_CallSetValue("cmi.interactions." + intInteractionIndex + ".latency", ConvertMilliSecondsIntoSCORM2004Time(intLatency)) && blnResult;
2133    }
2134
2135    if (strLearningObjectiveID != undefined && strLearningObjectiveID != null && strLearningObjectiveID != ""){
2136        blnResult = SCORM2004_CallSetValue("cmi.interactions." + intInteractionIndex + ".objectives.0.id", strLearningObjectiveID) && blnResult;
2137    }
2138
2139    blnResult = SCORM2004_CallSetValue("cmi.interactions." + intInteractionIndex + ".timestamp", ConvertDateToIso8601TimeStamp(dtmTime)) && blnResult;
2140
2141    WriteToDebug("Returning " + blnResult);
2142
2143    return blnResult;
2144}
2145
2146function SCORM2004_RecordTrueFalseInteraction(strID, blnResponse, blnCorrect, blnCorrectResponse, strDescription, intWeighting, intLatency, strLearningObjectiveID, dtmTime){
2147
2148    WriteToDebug("In SCORM2004_RecordTrueFalseInteraction strID=" + strID + ", strResponse=" + strResponse +
2149                    ", blnCorrect=" + blnCorrect + ", strCorrectResponse=" + strCorrectResponse +
2150                    ", strDescription=" + strDescription + ", intWeighting=" + intWeighting + ", intLatency=" + intLatency +
2151                    ", strLearningObjectiveID=" + strLearningObjectiveID + ", dtmTime=" + dtmTime);
2152
2153    var strResponse = null;
2154    var strCorrectResponse = null;
2155
2156    if (blnResponse){
2157        strResponse = "true";
2158    }
2159    else if (blnResponse !== null) {
2160        strResponse = "false";
2161    }
2162
2163    if (blnCorrectResponse == true){
2164        strCorrectResponse = "true";
2165    }
2166    else if(blnCorrectResponse == false){       //test for false b/c it could be null in which case we want to leave it as null
2167        strCorrectResponse = "false";
2168    }
2169
2170    return SCORM2004_RecordInteraction(strID, strResponse, blnCorrect, strCorrectResponse, strDescription, intWeighting, intLatency, strLearningObjectiveID, dtmTime, SCORM2004_INTERACTION_TYPE_TRUE_FALSE);
2171}
2172
2173
2174function SCORM2004_RecordMultipleChoiceInteraction(strID, aryResponse, blnCorrect, aryCorrectResponse, strDescription, intWeighting, intLatency, strLearningObjectiveID, dtmTime){
2175
2176    WriteToDebug("In SCORM2004_RecordMultipleChoiceInteraction strID=" + strID + ", aryResponse=" + aryResponse +
2177                    ", blnCorrect=" + blnCorrect + ", aryCorrectResponse=" + aryCorrectResponse +
2178                    ", strDescription=" + strDescription + ", intWeighting=" + intWeighting + ", intLatency=" + intLatency +
2179                    ", strLearningObjectiveID=" + strLearningObjectiveID + ", dtmTime=" + dtmTime);
2180
2181    var strResponse = null;
2182    var strCorrectResponse = "";
2183
2184    if (aryResponse !== null) {
2185        strResponse = "";
2186
2187        for (var i=0; i < aryResponse.length; i++){
2188
2189            if (strResponse.length > 0) {strResponse += "[,]";}
2190            strResponse += aryResponse[i].Long;
2191        }
2192    }
2193
2194    for (var i=0; i < aryCorrectResponse.length; i++){
2195
2196        if (strCorrectResponse.length > 0) {strCorrectResponse += "[,]";}
2197        strCorrectResponse += aryCorrectResponse[i].Long;
2198    }
2199
2200    return SCORM2004_RecordInteraction(strID, strResponse, blnCorrect, strCorrectResponse, strDescription, intWeighting, intLatency, strLearningObjectiveID, dtmTime, SCORM2004_INTERACTION_TYPE_CHOICE);
2201}
2202
2203
2204function SCORM2004_RecordFillInInteraction(strID, strResponse, blnCorrect, strCorrectResponse, strDescription, intWeighting, intLatency, strLearningObjectiveID, dtmTime){
2205
2206    WriteToDebug("In SCORM2004_RecordFillInInteraction strID=" + strID + ", strResponse=" + strResponse +
2207                    ", blnCorrect=" + blnCorrect + ", strCorrectResponse=" + strCorrectResponse +
2208                    ", strDescription=" + strDescription + ", intWeighting=" + intWeighting + ", intLatency=" + intLatency +
2209                    ", strLearningObjectiveID=" + strLearningObjectiveID + ", dtmTime=" + dtmTime);
2210
2211    var interactionType;
2212
2213    if (strCorrectResponse == null){
2214        strCorrectResponse = "";
2215    }
2216
2217    strCorrectResponse = new String(strCorrectResponse);
2218
2219    if (strCorrectResponse.length > 250 || strResponse.length > 250){
2220        interactionType = SCORM2004_INTERACTION_TYPE_LONG_FILL_IN;
2221    }
2222    else{
2223        interactionType = SCORM2004_INTERACTION_TYPE_FILL_IN;
2224    }
2225
2226    if (strCorrectResponse.length > 4000){strCorrectResponse = strCorrectResponse.substr(0, 4000);}
2227
2228    return SCORM2004_RecordInteraction(strID, strResponse, blnCorrect, strCorrectResponse, strDescription, intWeighting, intLatency, strLearningObjectiveID, dtmTime, interactionType);
2229}
2230
2231function SCORM2004_RecordMatchingInteraction(strID, aryResponse, blnCorrect, aryCorrectResponse, strDescription, intWeighting, intLatency, strLearningObjectiveID, dtmTime){
2232
2233    WriteToDebug("In SCORM2004_RecordMatchingInteraction strID=" + strID + ", aryResponse=" + aryResponse +
2234                    ", blnCorrect=" + blnCorrect + ", aryCorrectResponse=" + aryCorrectResponse +
2235                    ", strDescription=" + strDescription + ", intWeighting=" + intWeighting + ", intLatency=" + intLatency +
2236                    ", strLearningObjectiveID=" + strLearningObjectiveID + ", dtmTime=" + dtmTime);
2237
2238    var strResponse = null;
2239    var strCorrectResponse = "";
2240
2241    if (aryResponse !== null) {
2242        strResponse = "";
2243
2244        for (var i=0; i < aryResponse.length; i++){
2245
2246            if (strResponse.length > 0) {strResponse += "[,]";}
2247            strResponse += aryResponse[i].Source.Long + "[.]" + aryResponse[i].Target.Long;
2248        }
2249    }
2250
2251    for (var i=0; i < aryCorrectResponse.length; i++){
2252
2253        if (strCorrectResponse.length > 0) {strCorrectResponse += "[,]";}
2254        strCorrectResponse += aryCorrectResponse[i].Source.Long + "[.]" + aryCorrectResponse[i].Target.Long;
2255    }
2256
2257    return SCORM2004_RecordInteraction(strID, strResponse, blnCorrect, strCorrectResponse, strDescription, intWeighting, intLatency, strLearningObjectiveID, dtmTime, SCORM2004_INTERACTION_TYPE_MATCHING);
2258}
2259
2260function SCORM2004_RecordPerformanceInteraction(strID, strResponse, blnCorrect, strCorrectResponse, strDescription, intWeighting, intLatency, strLearningObjectiveID, dtmTime){
2261
2262    WriteToDebug("In SCORM2004_RecordPerformanceInteraction strID=" + strID + ", strResponse=" + strResponse +
2263                    ", blnCorrect=" + blnCorrect + ", strCorrectResponse=" + strCorrectResponse +
2264                    ", strDescription=" + strDescription + ", intWeighting=" + intWeighting + ", intLatency=" + intLatency +
2265                    ", strLearningObjectiveID=" + strLearningObjectiveID + ", dtmTime=" + dtmTime);
2266
2267    if (strResponse !== null) {
2268        strResponse = new String(strResponse);
2269        if (strResponse.length > 250){strResponse = strResponse.substr(0, 250);}
2270
2271        //we're only recording the step answer, not the step name
2272        strResponse = "[.]" + strResponse;
2273    }
2274
2275    if (strCorrectResponse == null){
2276        strCorrectResponse = "";
2277    }
2278
2279    strCorrectResponse = new String(strCorrectResponse);
2280    if (strCorrectResponse.length > 250){strCorrectResponse = strCorrectResponse.substr(0, 250);}
2281
2282    //we're only recording the step answer, not the step name
2283    strCorrectResponse = "[.]" + strCorrectResponse;
2284
2285    return SCORM2004_RecordInteraction(strID, strResponse, blnCorrect, strCorrectResponse, strDescription, intWeighting, intLatency, strLearningObjectiveID, dtmTime, SCORM2004_INTERACTION_TYPE_PERFORMANCE);
2286}
2287
2288function SCORM2004_RecordSequencingInteraction(strID, aryResponse, blnCorrect, aryCorrectResponse, strDescription, intWeighting, intLatency, strLearningObjectiveID, dtmTime){
2289
2290    WriteToDebug("In SCORM2004_RecordSequencingInteraction strID=" + strID + ", aryResponse=" + aryResponse +
2291                    ", blnCorrect=" + blnCorrect + ", aryCorrectResponse=" + aryCorrectResponse +
2292                    ", strDescription=" + strDescription + ", intWeighting=" + intWeighting + ", intLatency=" + intLatency +
2293                    ", strLearningObjectiveID=" + strLearningObjectiveID + ", dtmTime=" + dtmTime);
2294
2295    var strResponse = null;
2296    var strCorrectResponse = "";
2297
2298    if (aryResponse !== null) {
2299        strResponse = "";
2300
2301        for (var i=0; i < aryResponse.length; i++){
2302
2303            if (strResponse.length > 0) {strResponse += "[,]";}
2304            strResponse += aryResponse[i].Long;
2305        }
2306    }
2307
2308    for (var i=0; i < aryCorrectResponse.length; i++){
2309
2310        if (strCorrectResponse.length > 0) {strCorrectResponse += "[,]";}
2311        strCorrectResponse += aryCorrectResponse[i].Long;
2312    }
2313
2314    return SCORM2004_RecordInteraction(strID, strResponse, blnCorrect, strCorrectResponse, strDescription, intWeighting, intLatency, strLearningObjectiveID, dtmTime, SCORM2004_INTERACTION_TYPE_SEQUENCING);
2315}
2316
2317
2318function SCORM2004_RecordLikertInteraction(strID, response, blnCorrect, correctResponse, strDescription, intWeighting, intLatency, strLearningObjectiveID, dtmTime){
2319
2320    WriteToDebug("In RecordLikertInteraction strID=" + strID + ", response=" + response +
2321                    ", blnCorrect=" + blnCorrect + ", correctResponse=" + correctResponse +
2322                    ", strDescription=" + strDescription + ", intWeighting=" + intWeighting + ", intLatency=" + intLatency +
2323                    ", strLearningObjectiveID=" + strLearningObjectiveID + ", dtmTime=" + dtmTime);
2324
2325    var strResponse = null;
2326    var strCorrectResponse = "";
2327
2328    if (response !== null) {
2329        strResponse = response.Long;
2330    }
2331    if (correctResponse != null){
2332        strCorrectResponse = correctResponse.Long;
2333    }
2334
2335    return SCORM2004_RecordInteraction(strID, strResponse, blnCorrect, strCorrectResponse, strDescription, intWeighting, intLatency, strLearningObjectiveID, dtmTime, SCORM2004_INTERACTION_TYPE_LIKERT);
2336}
2337
2338function SCORM2004_RecordNumericInteraction(strID, strResponse, blnCorrect, strCorrectResponse, strDescription, intWeighting, intLatency, strLearningObjectiveID, dtmTime){
2339
2340    WriteToDebug("In SCORM2004_RecordNumericInteraction strID=" + strID + ", strResponse=" + strResponse +
2341                    ", blnCorrect=" + blnCorrect + ", strCorrectResponse=" + strCorrectResponse +
2342                    ", strDescription=" + strDescription + ", intWeighting=" + intWeighting + ", intLatency=" + intLatency +
2343                    ", strLearningObjectiveID=" + strLearningObjectiveID + ", dtmTime=" + dtmTime);
2344
2345
2346    if (strCorrectResponse != undefined && strCorrectResponse != null ) {
2347        //SCORM 2004 should use decimal ranges. If we receive a plain decimal we convert it to a range with the same
2348        //min and max values.
2349        if (IsValidDecimal(strCorrectResponse)) {
2350            strCorrectResponse = strCorrectResponse + "[:]" + strCorrectResponse;
2351            WriteToDebug("SCORM2004_RecordNumericInteraction received decimal correct response and converted to range, strCorrectResponse=" + strCorrectResponse);
2352        }
2353
2354        //Check that the range is valid. This checks both correct responses converted from decimals above and also non-decimal correct responses received.
2355        if (!IsValidDecimalRange(strCorrectResponse)) {
2356            WriteToDebug("Returning False - SCORM2004_RecordNumericInteraction received invali
2356d correct response decimal range, strCorrectResponse=" + strCorrectResponse);
2357            return false;
2358        }
2359
2360        //Handle numeric range interaction edge case.....
2361        //If the correct response is [:] and it's been indicated that the answer was incorrect we fail here. We _could_ accept this in SCORM2004 but rejecting it means
2362        //the behavior matches SCORM 1.2 where we reject such an interaction because we are unable to determine what the decimal correct response is (SCORM 1.2 does not accept ranges).
2363        //The interaction doesn't actually make sense. By this point we've ensured the response is a decimal, the correct response is any decimal..... yet it's indicated as incorrect???
2364        //In order to ensure both standards remain in lockstep with regard to this we call the same function that SCORM 1.2 uses to convert from a range to a decimal and if null is
2365        //returned we error
2366        if(ConvertDecimalRangeToDecimalBasedOnLearnerResponse(strCorrectResponse, strResponse, blnCorrect) === null) {
2367            WriteToDebug("Returning False - SCORM2004_RecordNumericInteraction received invalid correct response decimal range, response and correct indicator, strCorrectResponse=" +
2368                strCorrectResponse + ", strResponse=" + strResponse + ", blnCorrect=" + blnCorrect);
2369            return false;
2370        }
2371    }
2372
2373    return SCORM2004_RecordInteraction(strID, strResponse, blnCorrect, strCorrectResponse, strDescription, intWeighting, intLatency, strLearningObjectiveID, dtmTime, SCORM2004_INTERACTION_TYPE_NUMERIC);
2374}
2375
2376
2377
2378function SCORM2004_GetEntryMode(){
2379    var strEntry;
2380
2381    WriteToDebug("In SCORM2004_GetEntryMode");
2382
2383    SCORM2004_ClearErrorInfo();
2384
2385    strEntry = SCORM2004_CallGetValue("cmi.entry");
2386
2387    WriteToDebug("strEntry=" + strEntry);
2388
2389    if (strEntry == SCORM2004_ENTRY_ABINITIO){
2390        WriteToDebug("Returning first time");
2391        return ENTRY_FIRST_TIME;
2392    }
2393    else if (strEntry == SCORM2004_ENTRY_RESUME){
2394        WriteToDebug("Returning resume");
2395        return ENTRY_RESUME;
2396    }
2397    else if (strEntry == SCORM2004_ENTRY_NORMAL){
2398        WriteToDebug("returning normal");
2399        return ENTRY_REVIEW;
2400    }
2401    else{
2402        WriteToDebug("ERROR - invalid entry mode");
2403        SCORM2004_SetErrorInfoManually(SCORM2004_ERROR_INVALID_ENTRY,
2404                        "Invalid entry vocab received from LMS",
2405                        "strEntry=" + strEntry);
2406        return null;
2407    }
2408
2409}
2410
2411function SCORM2004_GetLessonMode(){
2412
2413    var strLessonMode;
2414
2415    WriteToDebug("In SCORM2004_GetLessonMode");
2416
2417    SCORM2004_ClearErrorInfo();
2418
2419    strLessonMode = SCORM2004_CallGetValue("cmi.mode");
2420
2421    WriteToDebug("strLessonMode=" + strLessonMode);
2422
2423    if (strLessonMode == SCORM2004_BROWSE){
2424        WriteToDebug("Returning browse");
2425        return MODE_BROWSE;
2426    }
2427    else if(strLessonMode == SCORM2004_NORMAL){
2428        WriteToDebug("returning normal");
2429        return MODE_NORMAL;
2430    }
2431    else if(strLessonMode == SCORM2004_REVIEW){
2432        WriteToDebug("Returning Review");
2433        return MODE_REVIEW;
2434    }
2435    else{
2436        WriteToDebug("ERROR - invalid lesson mode");
2437        SCORM2004_SetErrorInfoManually(SCORM2004_ERROR_INVALID_LESSON_MODE,
2438                                "Invalid lesson_mode vocab received from LMS",
2439                                "strLessonMode=" + strLessonMode);
2440        return null;
2441    }
2442
2443}
2444
2445function SCORM2004_GetTakingForCredit(){
2446
2447    var strCredit;
2448
2449    WriteToDebug("In SCORM2004_GetTakingForCredit");
2450
2451    SCORM2004_ClearErrorInfo();
2452
2453    strCredit = SCORM2004_CallGetValue("cmi.credit");
2454
2455    WriteToDebug("strCredit=" + strCredit);
2456
2457    if (strCredit == "credit"){
2458        WriteToDebug("Returning true");
2459        return true;
2460    }
2461    else if (strCredit == "no-credit"){
2462        WriteToDebug("Returning false");
2463        return false;
2464    }
2465    else{
2466        WriteToDebug("ERROR - invalid credit");
2467        SCORM2004_SetErrorInfoManually(SCORM2004_ERROR_INVALID_CREDIT,
2468                                  "Invalid credit vocab received from LMS",
2469                                  "strCredit=" + strCredit);
2470        return null;
2471    }
2472}
2473
2474
2475
2476function SCORM2004_SetObjectiveScore(strObjectiveID, intScore, intMaxScore, intMinScore){
2477
2478    var intObjectiveIndex;
2479    var blnResult;
2480    var fltNormalizedScore;
2481
2482    intScore = RoundToPrecision(intScore, 7);
2483    intMaxScore = RoundToPrecision(intMaxScore, 7);
2484    intMinScore = RoundToPrecision(intMinScore, 7);
2485
2486    WriteToDebug("In SCORM2004_SetObjectiveScore, strObejctiveID=" + strObjectiveID + ", intScore=" + intScore + ", intMaxScore=" + intMaxScore + ", intMinScore=" + intMinScore);
2487
2488    SCORM2004_ClearErrorInfo();
2489
2490    intObjectiveIndex = SCORM2004_FindObjectiveIndexFromID(strObjectiveID);
2491
2492    WriteToDebug("intObjectiveIndex=" + intObjectiveIndex);
2493
2494    fltNormalizedScore = RoundToPrecision(intScore / 100, 7);
2495
2496    blnResult = SCORM2004_CallSetValue("cmi.objectives." + intObjectiveIndex + ".id", strObjectiveID);
2497    blnResult = SCORM2004_CallSetValue("cmi.objectives." + intObjectiveIndex + ".score.raw", intScore) && blnResult;
2498    blnResult = SCORM2004_CallSetValue("cmi.objectives." + intObjectiveIndex + ".score.max", intMaxScore) && blnResult;
2499    blnResult = SCORM2004_CallSetValue("cmi.objectives." + intObjectiveIndex + ".score.min", intMinScore) && blnResult;
2500    blnResult = SCORM2004_CallSetValue("cmi.objectives." + intObjectiveIndex + ".score.scaled", fltNormalizedScore) && blnResult;
2501
2502    WriteToDebug("Returning " + blnResult);
2503
2504    return blnResult;
2505
2506}
2507
2508
2509function SCORM2004_SetObjectiveStatus(strObjectiveID, Lesson_Status){
2510
2511    var intObjectiveIndex;
2512    var blnResult;
2513    var strSCORMSuccessStatus = "";
2514    var strSCORMCompletionStatus = "";
2515
2516    WriteToDebug("In SCORM2004_SetObjectiveStatus strObjectiveID=" + strObjectiveID + ", Lesson_Status=" + Lesson_Status);
2517
2518    SCORM2004_ClearErrorInfo();
2519
2520    intObjectiveIndex = SCORM2004_FindObjectiveIndexFromID(strObjectiveID);
2521
2522    WriteToDebug("intObjectiveIndex=" + intObjectiveIndex);
2523
2524    if (Lesson_Status == LESSON_STATUS_PASSED){
2525        strSCORMSuccessStatus = SCORM2004_PASSED;
2526        strSCORMCompletionStatus = SCORM2004_COMPLETED;
2527    }
2528    else if (Lesson_Status == LESSON_STATUS_FAILED){
2529        strSCORMSuccessStatus = SCORM2004_FAILED;
2530        strSCORMCompletionStatus = SCORM2004_COMPLETED;
2531    }
2532    else if (Lesson_Status == LESSON_STATUS_COMPLETED){
2533        strSCORMSuccessStatus = SCORM2004_UNKNOWN;
2534        strSCORMCompletionStatus = SCORM2004_COMPLETED;
2535    }
2536    else if (Lesson_Status == LESSON_STATUS_BROWSED){
2537        strSCORMSuccessStatus = SCORM2004_UNKNOWN;
2538        strSCORMCompletionStatus = SCORM2004_COMPLETED;
2539    }
2540    else if (Lesson_Status == LESSON_STATUS_INCOMPLETE){
2541        strSCORMSuccessStatus = SCORM2004_UNKNOWN;
2542        strSCORMCompletionStatus = SCORM2004_INCOMPLETE;
2543    }
2544    else if (Lesson_Status == LESSON_STATUS_NOT_ATTEMPTED){
2545        strSCORMSuccessStatus = SCORM2004_UNKNOWN;
2546        strSCORMCompletionStatus = SCORM2004_NOT_ATTEMPTED;
2547    }
2548
2549    WriteToDebug("strSCORMSuccessStatus=" + strSCORMSuccessStatus);
2550    WriteToDebug("strSCORMCompletionStatus=" + strSCORMCompletionStatus);
2551
2552    blnResult = SCORM2004_CallSetValue("cmi.objectives." + intObjectiveIndex + ".id", strObjectiveID);
2553
2554    blnResult = SCORM2004_CallSetValue("cmi.objectives." + intObjectiveIndex + ".success_status", strSCORMSuccessStatus) && blnResult;
2555    blnResult = SCORM2004_CallSetValue("cmi.objectives." + intObjectiveIndex + ".completion_status", strSCORMCompletionStatus) && blnResult;
2556
2557    WriteToDebug("Returning " + blnResult);
2558
2559    return blnResult;
2560}
2561
2562
2563function SCORM2004_SetObjectiveDescription(strObjectiveID, strObjectiveDescription){
2564
2565    var intObjectiveIndex;
2566
2567    WriteToDebug("In SCORM2004_SetObjectiveDescription strObjectiveID=" + strObjectiveID + ", strObjectiveDescription=" + strObjectiveDescription);
2568
2569    SCORM2004_ClearErrorInfo();
2570
2571    intObjectiveIndex = SCORM2004_FindObjectiveIndexFromID(strObjectiveID);
2572
2573    WriteToDebug("intObjectiveIndex=" + intObjectiveIndex);
2574
2575
2576
2577    blnResult = SCORM2004_CallSetValue("cmi.objectives." + intObjectiveIndex + ".id", strObjectiveID);
2578    blnResult = SCORM2004_CallSetValue("cmi.objectives." + intObjectiveIndex + ".description", strObjectiveDescription) && blnResult;
2579
2580    WriteToDebug("Returning " + blnResult);
2581
2582    return blnResult;
2583}
2584
2585
2586function SCORM2004_GetObjectiveScore(strObjectiveID){
2587
2588    var intObjectiveIndex;
2589
2590    WriteToDebug("In SCORM2004_GetObjectiveScore, strObejctiveID=" + strObjectiveID);
2591
2592    SCORM2004_ClearErrorInfo();
2593
2594    intObjectiveIndex = SCORM2004_FindObjectiveIndexFromID(strObjectiveID);
2595
2596    WriteToDebug("intObjectiveIndex=" + intObjectiveIndex);
2597
2598    return SCORM2004_CallGetValue("cmi.objectives." + intObjectiveIndex + ".score.raw");
2599
2600}
2601
2602
2603function SCORM2004_GetObjectiveStatus(strObjectiveID){
2604
2605    var intObjectiveIndex;
2606    var strSuccessStatus;
2607    var strCompletionStatus;
2608
2609    WriteToDebug("In SCORM2004_GetObjectiveStatus, strObejctiveID=" + strObjectiveID);
2610
2611    SCORM2004_ClearErrorInfo();
2612
2613    intObjectiveIndex = SCORM2004_FindObjectiveIndexFromID(strObjectiveID);
2614
2615    WriteToDebug("intObjectiveIndex=" + intObjectiveIndex);
2616
2617    strSuccessStatus = SCORM2004_CallGetValue("cmi.objectives." + intObjectiveIndex + ".success_status");
2618    strCompletionStatus = SCORM2004_CallGetValue("cmi.objectives." + intObjectiveIndex + ".completion_status");
2619
2620    if (strSuccessStatus == SCORM2004_PASSED){
2621        WriteToDebug("returning Passed");
2622        return LESSON_STATUS_PASSED;
2623    }
2624    else if (strSuccessStatus == SCORM2004_FAILED){
2625        WriteToDebug("Returning Failed");
2626        return LESSON_STATUS_FAILED;
2627    }
2628    else if (strCompletionStatus == SCORM2004_COMPLETED){
2629        WriteToDebug("Returning Completed");
2630        return LESSON_STATUS_COMPLETED;
2631    }
2632    else if (strCompletionStatus == SCORM2004_INCOMPLETE){
2633        WriteToDebug("Returning Incomplete");
2634        return LESSON_STATUS_INCOMPLETE;
2635    }
2636    else if (strCompletionStatus == SCORM2004_NOT_ATTEMPTED || strCompletionStatus == SCORM2004_UNKNOWN || strCompletionStatus == ""){
2637        WriteToDebug("Returning Not Attempted");
2638        return LESSON_STATUS_NOT_ATTEMPTED;
2639    }
2640    else{
2641        WriteToDebug("ERROR - status not found");
2642        SCORM2004_SetErrorInfoManually(SCORM2004_ERROR_INVALID_STATUS,
2643                                "Invalid objective status received from LMS or initial status not yet recorded for objective",
2644                                "strCompletionStatus=" + strCompletionStatus);
2645        return null;
2646
2647    }
2648
2649}
2650
2651function SCORM2004_GetObjectiveProgressMeasure(strObjectiveID){
2652
2653    var strProgressMeasure = SCORM2004_CallGetValue("cmi.objectives." + strObjectiveID + ".progress_measure");
2654
2655    return strProgressMeasure;
2656
2657}
2658
2659
2660function SCORM2004_GetObjectiveDescription(strObjectiveID){
2661
2662    var intObjectiveIndex;
2663    var strSuccessStatus;
2664    var strCompletionStatus;
2665
2666    WriteToDebug("In SCORM2004_GetObjectiveDescription, strObejctiveID=" + strObjectiveID);
2667
2668    SCORM2004_ClearErrorInfo();
2669
2670    intObjectiveIndex = SCORM2004_FindObjectiveIndexFromID(strObjectiveID);
2671
2672    WriteToDebug("intObjectiveIndex=" + intObjectiveIndex);
2673
2674    strDescription = SCORM2004_CallGetValue("cmi.objectives." + intObjectiveIndex + ".description");
2675
2676    return strDescription;
2677
2678}
2679
2680function SCORM2004_FindObjectiveIndexFromID(strObjectiveID){
2681
2682    var intCount;
2683    var i;
2684    var strTempID;
2685
2686    WriteToDebug("In SCORM2004_FindObjectiveIndexFromID");
2687
2688    intCount = SCORM2004_CallGetValue("cmi.objectives._count");
2689
2690    if (intCount == ""){
2691        WriteToDebug("Setting intCount=0");
2692        return 0;
2693    }
2694
2695    intCount = parseInt(intCount, 10);
2696
2697    WriteToDebug("intCount=" + intCount);
2698
2699    for (i=0; i<intCount; i++){
2700
2701        WriteToDebug("Checking index " + i);
2702
2703        strTempID = SCORM2004_CallGetValue("cmi.objectives." + i + ".id");
2704
2705        WriteToDebug("ID=" + strTempID);
2706
2707        if (strTempID == strObjectiveID){
2708            WriteToDebug("Found Matching index");
2709            return i;
2710        }
2711    }
2712
2713    WriteToDebug("Did not find match, returning count");
2714
2715    return intCount;
2716}
2717
2718function SCORM2004_CreateValidIdentifier(str) {
2719    //
2720    // some customers don't like the identifiers being turned into URIs and don't
2721    // mind that this means that SCORM content may not be spec compliant, because
2722    // LMSs don't seem to validate (or reject) non-URI identifiers, so allow them
2723    // to use the process Driver used before the 6.x version where URIs became
2724    // properly handled
2725    //
2726    if (USE_LEGACY_IDENTIFIERS_FOR_2004) {
2727        return CreateValidIdentifierLegacy(str);
2728    }
2729
2730    return CreateUriIdentifier(str);
2731}
2732
2733
2734//---------------------------------------------------------------------------------
2735//Status Management Functions
2736
2737function SCORM2004_SetFailed(){
2738
2739    WriteToDebug("In SCORM2004_SetFailed");
2740
2741    var blnResult;
2742    var strCompletionStatus;
2743
2744    SCORM2004_ClearErrorInfo();
2745
2746    blnResult = SCORM2004_CallSetValue("cmi.success_status", SCORM2004_FAILED);
2747    if (PASS_FAIL_SETS_COMPLETION_FOR_2004) {
2748        blnResult = SCORM2004_CallSetValue("cmi.completion_status", SCORM2004_COMPLETED) && blnResult;
2749    } else {
2750        // If completion hasn't been set yet, then reset blnStatusWasSet so that SetReachedEnd can set it later.
2751        strCompletionStatus = SCORM2004_CallGetValue("cmi.completion_status");
2752        if (strCompletionStatus !== SCORM2004_COMPLETED) {
2753            WriteToDebug("Overriding blnStatusWasSet to false.");
2754            blnStatusWasSet = false;
2755        }
2756    }
2757    return blnResult;
2758}
2759
2760function SCORM2004_SetPassed(){
2761
2762    WriteToDebug("In SCORM2004_SetPassed");
2763
2764    var blnResult;
2765    var strCompletionStatus;
2766
2767    SCORM2004_ClearErrorInfo();
2768
2769    blnResult = SCORM2004_CallSetValue("cmi.success_status", SCORM2004_PASSED);
2770    if (PASS_FAIL_SETS_COMPLETION_FOR_2004) {
2771        blnResult = SCORM2004_CallSetValue("cmi.completion_status", SCORM2004_COMPLETED) && blnResult;
2772    } else {
2773        // If completion hasn't been set yet, then reset blnStatusWasSet so that SetReachedEnd can set it later.
2774        strCompletionStatus = SCORM2004_CallGetValue("cmi.completion_status");
2775        if (strCompletionStatus !== SCORM2004_COMPLETED) {
2776            WriteToDebug("Overriding blnStatusWasSet to false.");
2777            blnStatusWasSet = false;
2778        }
2779    }
2780    return blnResult;
2781}
2782
2783function  SCORM2004_SetCompleted(){
2784
2785    WriteToDebug("In SCORM2004_SetCompleted");
2786
2787    var blnResult;
2788
2789    SCORM2004_ClearErrorInfo();
2790
2791    blnResult = SCORM2004_CallSetValue("cmi.completion_status", SCORM2004_COMPLETED);
2792
2793    return blnResult;
2794}
2795
2796function SCORM2004_ResetStatus(){
2797
2798    WriteToDebug("In SCORM2004_ResetStatus");
2799
2800    var blnResult;
2801
2802    SCORM2004_ClearErrorInfo();
2803
2804    blnResult = SCORM2004_CallSetValue("cmi.success_status", SCORM2004_UNKNOWN);
2805    blnResult = SCORM2004_CallSetValue("cmi.completion_status", SCORM2004_INCOMPLETE) && blnResult;
2806
2807    return blnResult;
2808}
2809
2810function SCORM2004_GetStatus(){
2811
2812    var strSuccessStatus;
2813    var strCompletionStatus;
2814
2815    WriteToDebug("In SCORM2004_GetStatus");
2816
2817    SCORM2004_ClearErrorInfo();
2818
2819    strSuccessStatus = SCORM2004_CallGetValue("cmi.success_status");
2820    strCompletionStatus = SCORM2004_CallGetValue("cmi.completion_status");
2821
2822    WriteToDebug("strSuccessStatus=" + strSuccessStatus);
2823    WriteToDebug("strCompletionStatus=" + strCompletionStatus);
2824
2825    if (strSuccessStatus == SCORM2004_PASSED){
2826        WriteToDebug("returning Passed");
2827        return LESSON_STATUS_PASSED;
2828    }
2829    else if (strSuccessStatus == SCORM2004_FAILED){
2830        WriteToDebug("Returning Failed");
2831        return LESSON_STATUS_FAILED;
2832    }
2833    else if (strCompletionStatus == SCORM2004_COMPLETED){
2834        WriteToDebug("Returning Completed");
2835        return LESSON_STATUS_COMPLETED;
2836    }
2837    else if (strCompletionStatus == SCORM2004_INCOMPLETE){
2838        WriteToDebug("Returning Incomplete");
2839        return LESSON_STATUS_INCOMPLETE;
2840    }
2841    else if (strCompletionStatus == SCORM2004_NOT_ATTEMPTED || strCompletionStatus == SCORM2004_UNKNOWN){
2842        WriteToDebug("Returning Not Attempted");
2843        return LESSON_STATUS_NOT_ATTEMPTED;
2844    }
2845    else{
2846        WriteToDebug("ERROR - status not found");
2847        SCORM2004_SetErrorInfoManually(SCORM2004_ERROR_INVALID_STATUS,
2848                                "Invalid lesson status received from LMS",
2849                                "strCompletionStatus=" + strCompletionStatus);
2850        return null;
2851
2852    }
2853
2854
2855}
2856
2857//public
2858function SCORM2004_GetProgressMeasure(){
2859    WriteToDebug("In SCORM2004_GetProgressMeasure");
2860
2861    var blnResult;
2862
2863    SCORM2004_ClearErrorInfo();
2864
2865    blnResult = SCORM2004_CallGetValue("cmi.progress_measure");
2866
2867    return blnResult;
2868}
2869/*
2870    cmi.progress_measure cmi.completion_status
2871    0 Ònot attemptedÓ
2872    1 ÒcompletedÓ
2873    0 > value < 1 ÒincompleteÓ (typically, unless a cmi.completion_threshold is defined and the cmi.progress_measure is >= the cmi.completion_threshold)
2874
2875
2876    GetValue(Òcmi.progress_measureÓ)
2877    SetValue(Òcmi.progress_measureÓ,Ó0.75Ó)
2878    SetValue(Òcmi.progress_measureÓ,Ó1.0Ó)
2879
2880*/
2881
2882//public
2883function SCORM2004_SetProgressMeasure(numMeasure){
2884    WriteToDebug("In SCORM2004_SetProgressMeasure");
2885
2886    var blnResult;
2887
2888
2889    SCORM2004_ClearErrorInfo();
2890
2891    blnResult = SCORM2004_CallSetValue("cmi.progress_measure", numMeasure);
2892
2893    return blnResult;
2894}
2895
2896function SCORM2004_SetObjectiveProgressMeasure(strObjectiveID, numMeasure){
2897    WriteToDebug("In SCORM2004_SetObjectiveProgressMeasure");
2898    var intObjectiveIndex;
2899    var blnResult;
2900
2901    WriteToDebug("In SCORM2004_SetObjectiveProgressMeasure, strObejctiveID=" + strObjectiveID + ", numMeasure=" + numMeasure );
2902
2903    SCORM2004_ClearErrorInfo();
2904
2905    intObjectiveIndex = SCORM2004_FindObjectiveIndexFromID(strObjectiveID);
2906
2907    WriteToDebug("intObjectiveIndex=" + intObjectiveIndex);
2908
2909    SCORM2004_ClearErrorInfo();
2910
2911    blnResult = SCORM2004_CallSetValue("cmi.objectives." + intObjectiveIndex + ".progress_measure", numMeasure);
2912
2913    return blnResult;
2914}
2915
2916function SCORM2004_IsContentInBrowseMode(){
2917
2918    var strLessonMode
2919
2920    WriteToDebug("In SCORM2004_IsContentInBrowseMode");
2921
2922    strLessonMode = SCORM2004_CallGetValue("cmi.mode");
2923
2924    WriteToDebug("SCORM2004_IsContentInBrowseMode,  strLessonMode=" + strLessonMode);
2925
2926    if (strLessonMode == SCORM2004_BROWSE){
2927        WriteToDebug("Returning true");
2928        return true;
2929    }
2930    else{
2931        WriteToDebug("Returning false");
2932        return false;
2933    }
2934}
2935
2936
2937function SCORM2004_TranslateExitTypeToSCORM(strExitType){
2938
2939    WriteToDebug("In SCORM2004_TranslatgeExitTypeToSCORM strExitType-" + strExitType);
2940
2941    if (strExitType == EXIT_TYPE_SUSPEND){
2942        WriteToDebug("Returning suspend");
2943        return SCORM2004_SUSPEND;
2944    }
2945    else if (strExitType == EXIT_TYPE_UNLOAD){
2946        WriteToDebug("Returning Exit");
2947        return SCORM2004_NORMAL_EXIT;
2948    }
2949    else if (strExitType == EXIT_TYPE_FINISH){
2950        WriteToDebug("Returning Logout");
2951        return SCORM2004_NORMAL_EXIT;
2952    }
2953    else if (strExitType == EXIT_TYPE_TIMEOUT){
2954        WriteToDebug("Returning Timout");
2955        return SCORM2004_TIMEOUT;
2956    }
2957}
2958
2959
2960function SCORM2004_GetCompletionStatus(){
2961
2962    WriteToDebug("In SCORM2004_GetCompletionStatus");
2963
2964    return SCORM2004_COMPLETED;
2965
2966}
2967
2968function SCORM2004_SetPointBasedScore(intScore, intMaxScore, intMinScore){
2969
2970    var blnResult;
2971    var fltCalculatedScore;
2972
2973    WriteToDebug("In SCORM2004_SetPointBasedScore intScore=" + intScore + ", intMaxScore=" + intMaxScore + ", intMinScore=" + intMinScore);
2974
2975    SCORM2004_ClearErrorInfo();
2976
2977    if(intScore >= intMinScore)
2978    {
2979        fltCalculatedScore = intScore / intMaxScore;
2980    }else{
2981        WriteToDebug("intScore is lower than intMinScore. Overriding score with minscore for cmi.score.scaled");
2982        fltCalculatedScore = intMinScore / intMaxScore;
2983    }
2984    fltCalculatedScore = RoundToPrecision(fltCalculatedScore, 7);
2985
2986    blnResult = SCORM2004_CallSetValue("cmi.score.raw", intScore);
2987    blnResult = SCORM2004_CallSetValue("cmi.score.max", intMaxScore) && blnResult;
2988    blnResult = SCORM2004_CallSetValue("cmi.score.min", intMinScore) && blnResult;
2989
2990    blnResult = SCORM2004_CallSetValue("cmi.score.scaled", fltCalculatedScore) && blnResult;
2991
2992    WriteToDebug("Returning " + blnResult);
2993
2994    return blnResult;
2995
2996}
2997
2998
2999//___________________________________________________________
3000//Interaction Retrieval Functionality
3001//NOTE ON INTERACTION RETRIEVAL
3002//A.  It is only available in certain standards, standards where it is unavailable will return nothing
3003//B.  The interaction records are currently reported using "journaling", whereby each entry is appended
3004//      Retrieval methods will retrieve only the most recent value
3005
3006
3007//___________________________________________________________
3008//Helper Methods
3009function SCORM2004_FindInteractionIndexFromID(strInteractionID){
3010
3011    //with interactions, this method returns the index for the MOST RECENT (by timestamp)
3012    //interaction with this identifier
3013
3014    var intCount;
3015    var i;
3016    var strTempID;
3017    var dtmTempDate = new Date();
3018    var index;
3019    var currentIndexTimestamp = new Date("1/1/1900");
3020
3021
3022    WriteToDebug("In SCORM2004_FindInteractionIndexFromID");
3023
3024    intCount = SCORM2004_CallGetValue("cmi.interactions._count");
3025
3026    if (intCount == ""){
3027        WriteToDebug("Setting intCount=0");
3028        return null;
3029    }
3030
3031    intCount = parseInt(intCount, 10);
3032
3033    WriteToDebug("intCount=" + intCount);
3034
3035    for (i=0; i<intCount; i++){
3036
3037        WriteToDebug("Checking index " + i);
3038
3039        strTempID = SCORM2004_CallGetValue("cmi.interactions." + i + ".id");
3040
3041        WriteToDebug("ID=" + strTempID);
3042
3043        if (strTempID == strInteractionID){
3044            WriteToDebug("Found Matching index: " + i);
3045            //this is NOT a call to SCORM2004_GetInteractionTimestamp because that will use this method to loop
3046            dtmTempDate = ConvertIso8601TimeStampToDate(SCORM2004_CallGetValue("cmi.interactions." + i + ".timestamp"));
3047
3048            WriteToDebug("timestamp for " + i + ": " + dtmTempDate);
3049
3050            if (dtmTempDate>currentIndexTimestamp)
3051            {
3052                index = i;
3053                currentIndexTimestamp = dtmTempDate;
3054            }
3055        }
3056    }
3057
3058    if (index>=0) return index;
3059
3060    WriteToDebug("Did not find match, returning null");
3061
3062    return null;
3063}
3064//___________________________________________________________
3065
3066
3067function SCORM2004_GetInteractionType(strInteractionID)
3068{
3069
3070    var intInteractionIndex;
3071
3072    WriteToDebug("In SCORM2004_GetInteractionType, strInteractionID=" + strInteractionID);
3073
3074    SCORM2004_ClearErrorInfo();
3075
3076    intInteractionIndex = SCORM2004_FindInteractionIndexFromID(strInteractionID);
3077
3078
3079    if(intInteractionIndex == undefined || intInteractionIndex == null){
3080        return null;
3081    }
3082
3083
3084    WriteToDebug("intInteractionIndex=" + intInteractionIndex);
3085
3086    var type = SCORM2004_CallGetValue("cmi.interactions." + intInteractionIndex + ".type");
3087
3088    switch (type)
3089    {
3090        case SCORM2004_INTERACTION_TYPE_FILL_IN:
3091            return INTERACTION_TYPE_FILL_IN;
3092        case SCORM2004_INTERACTION_TYPE_LONG_FILL_IN:
3093            return INTERACTION_TYPE_LONG_FILL_IN;
3094        case SCORM2004_INTERACTION_TYPE_CHOICE:
3095            return INTERACTION_TYPE_CHOICE;
3096        case SCORM2004_INTERACTION_TYPE_LIKERT:
3097            return INTERACTION_TYPE_LIKERT;
3098        case SCORM2004_INTERACTION_TYPE_MATCHING:
3099            return INTERACTION_TYPE_MATCHING;
3100        case SCORM2004_INTERACTION_TYPE_NUMERIC:
3101            return INTERACTION_TYPE_NUMERIC;
3102        case SCORM2004_INTERACTION_TYPE_PERFORMANCE:
3103            return INTERACTION_TYPE_PERFORMANCE;
3104        case SCORM2004_INTERACTION_TYPE_SEQUENCING:
3105            return INTERACTION_TYPE_SEQUENCING;
3106        case SCORM2004_INTERACTION_TYPE_TRUE_FALSE:
3107            return INTERACTION_TYPE_TRUE_FALSE;
3108        default:
3109            return "";
3110    }
3111
3112
3113}
3114
3115//public
3116function SCORM2004_GetInteractionTimestamp(strInteractionID)
3117{
3118    WriteToDebug("In SCORM2004_GetInteractionTimestamp, strInteractionID=" + strInteractionID);
3119    var intInteractionIndex = SCORM2004_FindInteractionIndexFromID(strInteractionID);
3120    WriteToDebug("intInteractionIndex=" + intInteractionIndex);
3121
3122    SCORM2004_ClearErrorInfo();
3123
3124
3125
3126    if(intInteractionIndex == undefined || intInteractionIndex == null){
3127        return null;
3128    }
3129
3130
3131
3132    return SCORM2004_CallGetValue(ConvertIso8601TimeStampToDate("cmi.interactions." + intInteractionIndex + ".timestamp"));
3133
3134}
3135
3136
3137
3138//public
3139function SCORM2004_GetInteractionCorrectResponses(strInteractionID)
3140{
3141
3142    WriteToDebug("In SCORM2004_GetInteractionCorrectResponses, strInteractionID=" + strInteractionID);
3143
3144    var intInteractionIndex = SCORM2004_FindInteractionIndexFromID(strInteractionID);
3145    WriteToDebug("intInteractionIndex=" + intInteractionIndex);
3146
3147    SCORM2004_ClearErrorInfo();
3148
3149
3150    if(intInteractionIndex == undefined || intInteractionIndex == null){
3151        return null;
3152    }
3153
3154
3155    var strType = SCORM2004_CallGetValue("cmi.interactions." + intInteractionIndex + ".type");
3156
3157    var intCorrectResponseCount = SCORM2004_CallGetValue("cmi.interactions." + intInteractionIndex + ".correct_responses._count");
3158
3159
3160    if (intCorrectResponseCount == ""){
3161        WriteToDebug("Setting intCorrectResponseCount=0");
3162        return 0;
3163    }
3164
3165    intCorrectResponseCount = parseInt(intCorrectResponseCount, 10);
3166    WriteToDebug("intCorrectResponseCount=" + intCorrectResponseCount);
3167    if (intCorrectResponseCount==0) return new Array();
3168    if (intCorrectResponseCount>1) WriteToDebug("SCORM Driver is not currently implemented to support multiple correct response combinations and will only return the first");
3169
3170    var strResponse = new String(SCORM2004_CallGetValue("cmi.interactions." + intInteractionIndex + ".correct_responses.0.pattern"));
3171    var aryResponse = strResponse.split("[,]");
3172    WriteToDebug("aryResponse.length = " + aryResponse.length);
3173    aryResponse = SCORM2004_ProcessResponseArray(strType, aryResponse);
3174
3175    /*
3176    //this is the basis for handling N correct responses
3177    //because the API currently does not allow for the posting of multiple
3178    //correct response combinations, this functionality is not currently
3179    //implemented
3180    var aryResponses = new Array();
3181
3182    for (i=0; i<intCorrectResponseCount; i++)
3183    {
3184        WriteToDebug("examining CorrectResponse: " + i);
3185        var strResponse = new String(SCORM2004_CallGetValue("cmi.interactions." + intInteractionIndex + ".correct_responses." + i + ".pattern"));
3186        var aryResponse = strResponse.split("[,]");
3187        WriteToDebug("aryResponses.length = " + aryResponses.length);
3188        aryResponse = SCORM2004_ProcessResponseArray(strType, aryResponse);
3189        aryResponses.push(aryResponse);
3190    }
3191    */
3192
3193
3194
3195
3196    WriteToDebug("aryResponse.length = " + aryResponse.length);
3197    return aryResponse;
3198
3199
3200
3201}
3202
3203
3204
3205//public
3206function SCORM2004_GetInteractionWeighting(strInteractionID)
3207{
3208    WriteToDebug("In SCORM2004_GetInteractionWeighting, strInteractionID=" + strInteractionID);
3209
3210    var intInteractionIndex = SCORM2004_FindInteractionIndexFromID(strInteractionID);
3211    WriteToDebug("intInteractionIndex=" + intInteractionIndex);
3212
3213    SCORM2004_ClearErrorInfo();
3214
3215    if(intInteractionIndex == undefined || intInteractionIndex == null){
3216        return null;
3217    }
3218
3219    return SCORM2004_CallGetValue("cmi.interactions." + intInteractionIndex + ".weighting");
3220
3221}
3222
3223
3224
3225//public
3226function SCORM2004_GetInteractionLearnerResponses(strInteractionID)
3227{
3228    WriteToDebug("In SCORM2004_GetInteractionLearnerResponses, strInteractionID=" + strInteractionID);
3229    var intInteractionIndex = SCORM2004_FindInteractionIndexFromID(strInteractionID);
3230    WriteToDebug("intInteractionIndex=" + intInteractionIndex);
3231
3232    SCORM2004_ClearErrorInfo();
3233
3234    if(intInteractionIndex == undefined || intInteractionIndex == null){
3235        return null;
3236    }
3237
3238
3239    var strType = SCORM2004_CallGetValue("cmi.interactions." + intInteractionIndex + ".type");
3240
3241    var strResponse = new String(SCORM2004_CallGetValue("cmi.interactions." + intInteractionIndex + ".learner_response"));
3242
3243    var aryResponses = strResponse.split("[,]");
3244
3245    WriteToDebug("aryResponses.length = " + aryResponses.length);
3246
3247    aryResponses = SCORM2004_ProcessResponseArray(strType, aryResponses);
3248
3249    return aryResponses;
3250}
3251
3252function SCORM2004_ProcessResponseArray(strInteractionType, aryResponses)
3253{
3254    //process them (into ResponseIdentifiers, etc?)
3255    //only include the right amount of stuff
3256
3257    WriteToDebug("Processing Response Array with " + aryResponses.length + " pieces");
3258
3259
3260    for(var i=0; i<aryResponses.length; i++)
3261    {
3262        if (strInteractionType == SCORM2004_INTERACTION_TYPE_MATCHING)
3263        {
3264            WriteToDebug("processing matching type, i=" + i);
3265            aryResponses[i] = CreateMatchingResponse(aryResponses[i]);
3266        }
3267    }
3268
3269    return aryResponses;
3270
3271}
3272
3273//public
3274function SCORM2004_GetInteractionResult(strInteractionID)
3275{
3276    WriteToDebug("In SCORM2004_GetInteractionResult, strInteractionID=" + strInteractionID);
3277
3278    var intInteractionIndex = SCORM2004_FindInteractionIndexFromID(strInteractionID);
3279    WriteToDebug("intInteractionIndex=" + intInteractionIndex);
3280
3281    SCORM2004_ClearErrorInfo();
3282
3283    if(intInteractionIndex == undefined || intInteractionIndex == null){
3284        return null;
3285    }
3286
3287
3288    return SCORM2004_CallGetValue("cmi.interactions." + intInteractionIndex + ".result");
3289
3290
3291}
3292
3293
3294
3295//public
3296function SCORM2004_GetInteractionLatency(strInteractionID)
3297{
3298    WriteToDebug("In SCORM2004_GetInteractionLatency, strInteractionID=" + strInteractionID);
3299    var intInteractionIndex = SCORM2004_FindInteractionIndexFromID(strInteractionID);
3300    WriteToDebug("intInteractionIndex=" + intInteractionIndex);
3301
3302    SCORM2004_ClearErrorInfo();
3303
3304    if(intInteractionIndex == undefined || intInteractionIndex == null){
3305        return null;
3306    }
3307
3308    var strLatency = SCORM2004_CallGetValue("cmi.interactions." + intInteractionIndex + ".latency");
3309
3310    WriteToDebug("latency returns: " + strLatency);
3311    var intLatency = ConvertScorm2004TimeToMS(strLatency);
3312    WriteToDebug("latency in milliseconds: " + intLatency);
3313
3314    return intLatency;
3315
3316}
3317
3318
3319
3320//public
3321function SCORM2004_GetInteractionDescription(strInteractionID)
3322{
3323    WriteToDebug("In SCORM2004_GetInteractionDescription, strInteractionID=" + strInteractionID);
3324    var intInteractionIndex = SCORM2004_FindInteractionIndexFromID(strInteractionID);
3325    WriteToDebug("intInteractionIndex=" + intInteractionIndex);
3326
3327    SCORM2004_ClearErrorInfo();
3328
3329    if(intInteractionIndex == undefined || intInteractionIndex == null){
3330        return null;
3331    }
3332
3333
3334    return SCORM2004_CallGetValue("cmi.interactions." + intInteractionIndex + ".description");
3335
3336
3337}
3338
3339//________________________________________________
3340
3341
3342//public
3343function SCORM2004_CreateDataBucket(strBucketId, intMinSize, intMaxSize, strPersistenceType){
3344
3345    WriteToDebug("In SCORM2004_CreateDataBucket, strBucketId=" + strBucketId + ", intMinSize=" + intMinSize + ", intMaxSize=" + intMaxSize + ", 
3345course=" + strPersistenceType);
3346
3347    if (SCORM2004_DetectSSPSupport()){
3348
3349        if (SCORM2004_DoesBucketExist(strBucketId) == true){
3350            WriteToDebug("Bucket already exists and can't be re-allocated.");
3351            return false;
3352        }
3353        else{
3354            return SCORM2004_CallSetValue("ssp.allocate", "{bucketID=" + strBucketId + "}{requested=" + intMaxSize + "}{minimum=" + intMinSize + "}{reducible=true}{persistence="+ strPersistenceType +"}")//")
3355        }
3356    }
3357    else{
3358        WriteToDebug("SSP is not supported in this LMS, returning false.");
3359        return false;
3360    }
3361}
3362
3363//public
3364function SCORM2004_GetDataFromBucket(strBucketId){
3365    WriteToDebug("In SCORM2004_GetDataFromBucket, strBucketId=" + strBucketId);
3366
3367    if (SCORM2004_DetectSSPSupport()){
3368        var data = SCORM2004_CallGetValue("ssp.data.{bucketID=" + strBucketId + "}");
3369        return data;
3370    }
3371    else{
3372        WriteToDebug("SSP is not supported in this LMS, returning empty string.");
3373        return "";
3374    }
3375}
3376
3377//public
3378function SCORM2004_PutDataInBucket(strBucketId, strData, blnAppendToEnd){
3379    WriteToDebug("In SCORM2004_PutDataInBucket, strBucketId=" + strBucketId + ", blnAppendToEnd=" + blnAppendToEnd + ", strData=" + strData);
3380
3381    if (SCORM2004_DetectSSPSupport()){
3382
3383        if (blnAppendToEnd == true){
3384            return SCORM2004_CallSetValue("ssp.appendData", "{bucketID=" + strBucketId + "}" + strData);
3385        }
3386        else{
3387            return SCORM2004_CallSetValue("ssp.data", "{bucketID=" + strBucketId + "}" + strData);
3388        }
3389    }
3390    else{
3391        WriteToDebug("SSP is not supported in this LMS, returning false.");
3392        return false;
3393    }
3394}
3395
3396//public
3397function SCORM2004_DetectSSPSupport(){
3398
3399    WriteToDebug("In SCORM2004_DetectSSPSupport");
3400
3401    if (blnSCORM2004_SSP_Is_Supported == true){
3402        WriteToDebug("Support already detected, returning true");
3403        return true;
3404    }
3405
3406    else if (blnSCORM2004_SSP_Is_Supported == false){
3407        WriteToDebug("Support already determined to me missing, returning false");
3408        return false;
3409    }
3410
3411    else{
3412        //make an error-free call to the SSP data model to see if it is supported
3413        var intBucketCount = SCORM2004_CallGetValue("ssp._count");
3414
3415        //if the call succeed, then SSP is supported, otherwise it is not
3416        if (SCORM2004_GetLastError() == NO_ERROR){
3417            WriteToDebug("SSP data model call succeeded, SSP is supported");
3418            blnSCORM2004_SSP_Is_Supported = true;
3419            return true;
3420        }
3421        else{
3422            WriteToDebug("SSP data model call failed, SSP is NOT supported");
3423            blnSCORM2004_SSP_Is_Supported = false;
3424            return false;
3425        }
3426    }
3427}
3428
3429//public
3430function SCORM2004_GetBucketInfo(strBucketId){
3431
3432    WriteToDebug("In SCORM2004_GetBucketInfo, strBucketId=" + strBucketId);
3433
3434    var intTotalSpace = 0;
3435    var intUsedSpace = 0;
3436
3437    var strBucketState = new String(SCORM2004_CallGetValue("ssp.bucket_state.{bucketID=" + strBucketId + "}"));
3438
3439    if (strBucketState == "" || strBucketState == null || strBucketState == undefined){
3440        WriteToDebug ("Could not retrieve bucket state, returning 0 total size and 0 used size");
3441        return new SSPBucketSize(0, 0);
3442    }
3443
3444    var sectionArray = strBucketState.split("{");
3445
3446    for (var section in sectionArray){
3447
3448        section = new String(sectionArray[section]);
3449
3450        section = section.replace("}", "");
3451
3452        if (section.indexOf("totalSpace", 0) == 0){
3453            WriteToDebug("Found total space");
3454            intTotalSpace = parseInt(section.substr(11), 10);
3455            WriteToDebug("total space=" + intTotalSpace);
3456        }
3457        else if (section.indexOf("used", 0) == 0){
3458            WriteToDebug("Found used space");
3459            intUsedSpace = parseInt(section.substr(5), 10);
3460            WriteToDebug("used=" + intUsedSpace);
3461        }
3462    }
3463
3464    var returnValue = new SSPBucketSize(intTotalSpace, intUsedSpace);
3465
3466    return returnValue;
3467}
3468
3469//private
3470function SCORM2004_DoesBucketExist(strBucketId){
3471
3472    WriteToDebug("In SCORM2004_DoesBucketExist, strBucketId=" + strBucketId);
3473
3474    var intBucketCount = SCORM2004_CallGetValue("ssp._count");
3475    intBucketCount = parseInt(intBucketCount, 10);
3476
3477    for (var i=0; i < intBucketCount; i++){
3478        if (strBucketId == SCORM2004_CallGetValue("ssp." + i + ".id")){
3479            WriteToDebug("Bucket '" + strBucketId + "' Exists");
3480            return true;
3481        }
3482    }
3483
3484    WriteToDebug("Bucket '" + strBucketId + "' DOES NOT Exist");
3485    return false;
3486}
3487
3488function SCORM2004_SetNavigationRequest(strNavRequest){
3489     WriteToDebug("In SCORM2004_SetNavigationRequest, strNavRequest=" + strNavRequest);
3490    SCORM2004_ClearErrorInfo();
3491    //first check to see if it is a choice nav request
3492    var regValidChoice = /^\{target=[.A-Za-z0-9_-]+\}choice$/;
3493    if (strNavRequest.match(regValidChoice)){
3494        SCORM2004_CallSetValue("adl.nav.request",strNavRequest);
3495        return true;
3496    }else{
3497        //check for other valid options
3498        switch(strNavRequest){
3499            case "continue":
3500                break;
3501            case "previous":
3502                break;
3503            case "exit":
3504                break;
3505            case "exitAll":
3506                break;
3507            case "abandon":
3508                break;
3509            case "abandonAll":
3510                break;
3511            case "suspendAll":
3512                break;
3513            case "_none_":
3514                break;
3515            default:
3516            WriteToDebug("In SCORM2004_SetNavigationRequest, NavRequest is not valid - strNavRequest=" + strNavRequest);
3517                return false;
3518        }
3519        SCORM2004_CallSetValue("adl.nav.request",strNavRequest);
3520        return true;
3521    }
3522}
3523
3524function SCORM2004_GetNavigationRequest(){
3525    WriteToDebug("In SCORM2004_GetNavigationRequest");
3526    SCORM2004_ClearErrorInfo();
3527    return SCORM2004_CallGetValue("adl.nav.request");
3528}
3529
3530/*
3531o “continue”: Indicates to the LMS that the content asserts that a Continue navigation request should be processed immediately following the SCO’s termination.
3532o “previous”: Indicates to the LMS that the content asserts that a Previous navigation request should be processed immediately following the SCO’s termination.
3533o “choice”: Indicates to the LMS that the content asserts that a Choice navigation request should be processed immediately following the SCO’s termination.
3534o “exit”: Indicates to the LMS that the content asserts that an Exit navigation request should be processed immediately following the SCO’s termination.
3535o “exitAll”: Indicates to the LMS that the content asserts that an Exit All navigation request should be processed immediately following the SCO’s termination.
3536o “abandon”: Indicates to the LMS that the content asserts that an Abandon navigation request should be processed immediately following the SCO’s termination.
3537o “abandonAll”: Indicates to the LMS that the content asserts that an Abandon All navigation request should be processed immediately following the SCO’s termination.
3538o “suspendAll”: Indicates to the LMS that the content asserts that a Suspend All navigation request should be processed immediately following the SCO’s termination.
3539o “_none_”: Indicates to the LMS that the content asserts that any previous navigation request indicated by the SCO should not be processed immediately following the SCO’s termination. Setting this value effectively clears any pending navigation request.
3540*/
3541
3542//---------------------------------------------------------------------------------
3543//Functions to Call the SCORM API
3544
3545//note: in all functions that interact with API, we concact any returned strings with "" to convert
3546//the value to a string type, do this b/c many SCORM API's will return a Java String instead of a Javascript
3547//string
3548
3549
3550function SCORM2004_CallInitialize(){
3551
3552    var strResult;
3553
3554    WriteToDebug("In SCORM2004_CallInitialize");
3555
3556    SCORM2004_objAPI = SCORM2004_GrabAPI();
3557
3558    WriteToDebug("Calling Initialize");
3559
3560    strResult = SCORM2004_objAPI.Initialize("");
3561    strResult = strResult + "";
3562
3563    WriteToDebug("strResult=" + strResult);
3564
3565    if (strResult == SCORM2004_FALSE){
3566
3567        WriteToDebug("Detected failed call to initialize");
3568
3569        SCORM2004_SetErrorInfo();
3570
3571        WriteToDebug ("Error calling Initialize:");
3572        WriteToDebug ("              Error Number=" + intSCORM2004Error);
3573        WriteToDebug ("              Error String=" + strSCORM2004ErrorString);
3574        WriteToDebug ("              Diagnostic=" + strSCORM2004ErrorDiagnostic);
3575
3576        return false;
3577
3578    }
3579
3580    WriteToDebug("Returning true");
3581
3582    return true;
3583}
3584
3585function SCORM2004_CallSetValue(strElement, strValue){
3586
3587    var strResult;
3588
3589    WriteToDebug("SCORM2004_CallSetValue strElement=" + strElement + ", strValue=" + strValue);
3590
3591    if (blnReviewModeSoReadOnly === true){
3592        WriteToDebug("Mode is Review and configuration setting dictates this should be read only so exiting.");
3593        return true;
3594    }
3595
3596    SCORM2004_objAPI = SCORM2004_GrabAPI();
3597
3598    WriteToDebug("Calling SetValue");
3599
3600    strElement = strElement + "";
3601    strValue = strValue + "";
3602
3603    strResult = SCORM2004_objAPI.SetValue(strElement, strValue)
3604    strResult = strResult + "";
3605
3606    WriteToDebug("strResult=" + strResult);
3607
3608    if (strResult == SCORM2004_FALSE){
3609
3610        WriteToDebug("Detected Failed call to SetValue");
3611
3612        SCORM2004_SetErrorInfo();
3613
3614        WriteToDebug ("Error calling SetValue:");
3615        WriteToDebug ("              strElement=" + strElement);
3616        WriteToDebug ("              strValue=" + strValue);
3617        WriteToDebug ("              Error Number=" + intSCORM2004Error);
3618        WriteToDebug ("              Error String=" + strSCORM2004ErrorString);
3619        WriteToDebug ("              Diagnostic=" + strSCORM2004ErrorDiagnostic);
3620
3621        return false;
3622    }
3623
3624    WriteToDebug("Returning true");
3625
3626    return true;
3627}
3628
3629function SCORM2004_CallGetValue(strElement){
3630
3631    var strResult
3632
3633    WriteToDebug("In SCORM2004_CallGetValue strElement=" + strElement);
3634
3635    SCORM2004_objAPI = SCORM2004_GrabAPI();
3636
3637    WriteToDebug("Call GetValue");
3638
3639    strElement = strElement + "";
3640
3641    strResult = SCORM2004_objAPI.GetValue(strElement) + ""
3642
3643    WriteToDebug("strResult=" + strResult);
3644
3645    intSCORM2004Error = SCORM2004_objAPI.GetLastError()
3646    intSCORM2004Error = intSCORM2004Error + "";
3647
3648    WriteToDebug("intSCORM2004Error=" + intSCORM2004Error);
3649
3650    if (intSCORM2004Error != SCORM2004_NO_ERROR){
3651
3652        WriteToDebug("Detected failed called to GetValue");
3653
3654        SCORM2004_SetErrorInfo();
3655
3656        WriteToDebug ("Error calling LMSGetValue:");
3657        WriteToDebug ("              strElement=" + strElement);
3658        WriteToDebug ("              Error Number=" + intSCORM2004Error);
3659        WriteToDebug ("              Error String=" + strSCORM2004ErrorString);
3660        WriteToDebug ("              Diagnostic=" + strSCORM2004ErrorDiagnostic);
3661    }
3662
3663    WriteToDebug("Returning " + strResult);
3664
3665    return strResult;
3666
3667}
3668
3669
3670function SCORM2004_CallCommit(){
3671
3672    var strResult;
3673
3674    WriteToDebug("In SCORM2004_CallCommit");
3675
3676    SCORM2004_objAPI = SCORM2004_GrabAPI();
3677
3678    WriteToDebug("Calling Commit");
3679
3680    strResult = SCORM2004_objAPI.Commit("");
3681
3682    strResult = strResult + "";
3683
3684    WriteToDebug("strResult=" + strResult);
3685
3686    if (strResult == SCORM2004_FALSE){
3687
3688        WriteToDebug("Detected failed call to Commit");
3689
3690        SCORM2004_SetErrorInfo();
3691
3692        WriteToDebug ("Error calling Commit:");
3693        WriteToDebug ("              Error Number=" + intSCORM2004Error);
3694        WriteToDebug ("              Error String=" + strSCORM2004ErrorString);
3695        WriteToDebug ("              Diagnostic=" + strSCORM2004ErrorDiagnostic);
3696
3697        return false;
3698    }
3699
3700    WriteToDebug("Returning true");
3701
3702    return true;
3703}
3704
3705
3706function SCORM2004_CallTerminate(){
3707
3708    var strResult;
3709
3710    WriteToDebug("In SCORM2004_CallTerminate");
3711
3712    SCORM2004_objAPI = SCORM2004_GrabAPI();
3713
3714    WriteToDebug("Calling Terminate");
3715
3716    strResult = SCORM2004_objAPI.Terminate("");
3717
3718    strResult = strResult + "";
3719
3720    WriteToDebug("strResult=" + strResult);
3721
3722    if (strResult == SCORM2004_FALSE){
3723
3724        WriteToDebug("Detected failed call to Terminate");
3725
3726        SCORM2004_SetErrorInfo();
3727
3728        WriteToDebug ("Error calling Terminate:");
3729        WriteToDebug ("              Error Number=" + intSCORM2004Error);
3730        WriteToDebug ("              Error String=" + strSCORM2004ErrorString);
3731        WriteToDebug ("              Diagnostic=" + strSCORM2004ErrorDiagnostic);
3732
3733        return false;
3734    }
3735
3736    WriteToDebug("Returning True");
3737
3738    return true;
3739}
3740
3741
3742//---------------------------------------------------------------------------------
3743//Error Handling Functions
3744function SCORM2004_ClearErrorInfo(){
3745
3746    WriteToDebug("In SCORM2004_ClearErrorInfo");
3747
3748    intSCORM2004Error = SCORM2004_NO_ERROR;
3749    strSCORM2004ErrorString = "";
3750    strSCORM2004ErrorDiagnostic = "";
3751}
3752
3753function SCORM2004_SetErrorInfo(){
3754
3755    WriteToDebug("In SCORM2004_SetErrorInfo");
3756
3757    intSCORM2004Error = SCORM2004_objAPI.GetLastError();
3758    strSCORM2004ErrorString = SCORM2004_objAPI.GetErrorString(intSCORM2004Error);
3759    strSCORM2004ErrorDiagnostic = SCORM2004_objAPI.GetDiagnostic("");
3760
3761    intSCORM2004Error = intSCORM2004Error + "";
3762    strSCORM2004ErrorString = strSCORM2004ErrorString + "";
3763    strSCORM2004ErrorDiagnostic = strSCORM2004ErrorDiagnostic + "";
3764
3765    WriteToDebug("intSCORM2004Error=" + intSCORM2004Error);
3766    WriteToDebug("strSCORM2004ErrorString=" + strSCORM2004ErrorString);
3767    WriteToDebug("strSCORM2004ErrorDiagnostic=" + strSCORM2004ErrorDiagnostic);
3768}
3769
3770function SCORM2004_SetErrorInfoManually(intNum, strString, strDiagnostic){
3771
3772        WriteToDebug("In SCORM2004_SetErrorInfoManually");
3773        WriteToDebug("ERROR-Num=" + intNum);
3774        WriteToDebug("      String=" + strString);
3775        WriteToDebug("      Diag=" + strDiagnostic);
3776
3777        intSCORM2004Error = intNum;
3778        strSCORM2004ErrorString = strString;
3779        strSCORM2004ErrorDiagnostic = strDiagnostic;
3780
3781}
3782
3783function SCORM2004_GetLastError(){
3784
3785    WriteToDebug("In SCORM2004_GetLastError");
3786
3787    if (intSCORM2004Error == SCORM2004_NO_ERROR){
3788        WriteToDebug("Returning No Error");
3789        return NO_ERROR;
3790    }
3791    else {
3792        WriteToDebug("Returning " + intSCORMError);
3793        return intSCORM2004Error;
3794    }
3795}
3796
3797function SCORM2004_GetLastErrorDesc(){
3798    WriteToDebug("In SCORM2004_GetLastErrorDesc, " + strSCORM2004ErrorString + "\n" + strSCORM2004ErrorDiagnostic);
3799    return strSCORM2004ErrorString + "\n" + strSCORM2004ErrorDiagnostic;
3800}
3801
3802
3803
3804//---------------------------------------------------------------------------------
3805//API Locating Functions
3806
3807function SCORM2004_GrabAPI(){
3808
3809    WriteToDebug("In SCORM2004_GrabAPI");
3810
3811    if (typeof(SCORM2004_objAPI) == "undefined" || SCORM2004_objAPI == null){
3812        WriteToDebug("Searching with Rustici Software algorithm");
3813        SCORM2004_objAPI = SCORM2004_GetAPI();
3814    }
3815
3816    if (typeof(SCORM2004_objAPI) == "undefined" || SCORM2004_objAPI == null || SCORM2004_objAPI ==false){
3817        WriteToDebug("Searching with SearchForAPI");
3818        SCORM2004_objAPI = SCORM2004_SearchForAPI(window);
3819    }
3820
3821    WriteToDebug("Grab API, returning, found API = " + (SCORM2004_objAPI != null));
3822
3823    return SCORM2004_objAPI;
3824
3825}
3826
3827
3828
3829
3830/*******************************************************************
3831    * SCORM 2004 API Search Algorithm
3832    * Description - Improvement of the algorithm developed by ADL to
3833        find the SCORM 2004 API Adapter. The improvements eliminate
3834        errors, improve code clarity and eliminate the dependence
3835        on global variables. The errors removed include:
3836            - The "win" variable was never declared in a scope
3837              accessible to the GetAPI function
3838            - A call to API.version which is not part of the SCORM
3839              2004 specification
3840            - The previous algorithm was not able to find the API
3841              if it was located in the window's parent's opener
3842    * Original Author - ADL & Concurrent Technologies Corporation
3843    * Author -  Mike Rustici (April 1, 2004)
3844                Rustici Software, LLC
3845                http://www.scorm.com
3846                [email protected]
3847*******************************************************************/
3848
3849
3850
3851
3852/*
3853ScanParentsForApi
3854-Searches all the parents of a given window until
3855 it finds an object named "API_1484_11". If an
3856 object of that name is found, a reference to it
3857 is returned. Otherwise, this function returns null.
3858*/
3859function SCORM2004_ScanParentsForApi(win)
3860{
3861
3862    /*
3863    Establish an outrageously high maximum number of
3864    parent windows that we are will to search as a
3865    safe guard against an infinite loop. This is
3866    probably not strictly necessary, but different
3867    browsers can do funny things with undefined objects.
3868    */
3869    var MAX_PARENTS_TO_SEARCH = 500;
3870    var nParentsSearched = 0;
3871
3872    /*
3873    Search each parent window until we either:
3874         -find the API,
3875         -encounter a window with no parent (parent is null
3876                or the same as the current window)
3877         -or, have reached our maximum nesting threshold
3878    */
3879    while ( (win.API_1484_11 == null || win.API_1484_11 == undefined) &&
3880            (win.parent != null) && (win.parent != win) &&
3881            (nParentsSearched <= MAX_PARENTS_TO_SEARCH)
3882          )
3883    {
3884
3885        nParentsSearched++;
3886        win = win.parent;
3887    }
3888
3889    /*
3890    If the API doesn't exist in the window we stopped looping on,
3891    then this will return null.
3892    */
3893    return win.API_1484_11;
3894}
3895
3896
3897/*
3898GetAPI
3899-Searches all parent and opener windows relative to the
3900 current window for the SCORM 2004 API Adapter.
3901 Returns a reference to the API Adapter if found or null
3902 otherwise.
3903*/
3904function SCORM2004_GetAPI()
3905{
3906
3907    var API = null;
3908
3909    //Search all the parents of the current window if there are any
3910    if ((window.parent != null) && (window.parent != window))
3911    {
3912        API = SCORM2004_ScanParentsForApi(window.parent);
3913    }
3914
3915    /*
3916    If we didn't find the API in this window's chain of parents,
3917    then search all the parents of the opener window if there is one
3918    */
3919    if ((API == null) && (window.top.opener != null))
3920    {
3921        API = SCORM2004_ScanParentsForApi(window.top.opener);
3922    }
3923
3924    return API;
3925}
3926
3927function SCORM2004_SearchForAPI(wndLookIn){
3928
3929    WriteToDebug("SCORM2004_SearchForAPI");
3930
3931    var objAPITemp = null;
3932    var strDebugID = "";
3933
3934    strDebugID = "Name=" + wndLookIn.name + ", href=" + wndLookIn.location.href
3935
3936    objAPITemp = wndLookIn.API_1484_11;
3937
3938    if (SCORM2004_APIFound(objAPITemp)){
3939        WriteToDebug("Found API in this window - "  + strDebugID);
3940        return objAPITemp;
3941    }
3942
3943    if (SCORM2004_WindowHasParent(wndLookIn)){
3944        WriteToDebug("Searching Parent - "  + strDebugID);
3945        objAPITemp = SCORM2004_SearchForAPI(wndLookIn.parent);
3946    }
3947
3948    if (SCORM2004_APIFound(objAPITemp)){
3949        WriteToDebug("Found API in a parent - "  + strDebugID);
3950        return objAPITemp;
3951    }
3952
3953    if (SCORM2004_WindowHasOpener(wndLookIn)){
3954        WriteToDebug("Searching Opener - "  + strDebugID);
3955        objAPITemp = SCORM2004_SearchForAPI(wndLookIn.opener);
3956    }
3957
3958    if (SCORM2004_APIFound(objAPITemp)){
3959        WriteToDebug("Found API in an opener - "  + strDebugID);
3960        return objAPITemp;
3961    }
3962
3963    //look in child frames individually, don't call this function recursively
3964    //on them to prevent an infinite loop when it looks back up to the parents
3965    WriteToDebug("Looking in children - "  + strDebugID);
3966    objAPITemp = SCORM2004_LookInChildren(wndLookIn);
3967
3968    if (SCORM2004_APIFound(objAPITemp)){
3969        WriteToDebug("Found API in Children - "  + strDebugID);
3970        return objAPITemp;
3971    }
3972
3973    WriteToDebug("Didn't find API in this window - "  + strDebugID);
3974    return null;
3975}
3976
3977function SCORM2004_LookInChildren(wnd){
3978
3979    WriteToDebug("SCORM2004_LookInChildren");
3980
3981    var objAPITemp = null;
3982
3983    var strDebugID = "";
3984
3985    strDebugID = "Name=" + wnd.name + ", href=" + wnd.location.href
3986
3987    for (var i=0; i < wnd.frames.length; i++){
3988
3989        WriteToDebug("Looking in child frame " + i);
3990
3991        objAPITemp = wnd.frames[i].API_1484_11;
3992
3993        if (SCORM2004_APIFound(objAPITemp)){
3994            WriteToDebug("Found API in child frame of " + strDebugID);
3995            return objAPITemp;
3996        }
3997
3998        WriteToDebug("Looking in this child's children " + strDebugID);
3999        objAPITemp = SCORM2004_LookInChildren(wnd.frames[i]);
4000
4001        if (SCORM2004_APIFound(objAPITemp)){
4002            WriteToDebug("API found in this child's children " + strDebugID);
4003            return objAPITemp;
4004        }
4005    }
4006
4007    return null;
4008}
4009
4010function SCORM2004_WindowHasOpener(wnd){
4011    WriteToDebug("In SCORM2004_WindowHasOpener");
4012    if ((wnd.opener != null) && (wnd.opener != wnd) && (typeof(wnd.opener) != "undefined")){
4013        WriteToDebug("Window Does Have Opener");
4014        return true;
4015    }
4016    else{
4017        WriteToDebug("Window Does Not Have Opener");
4018        return false;
4019    }
4020}
4021
4022function SCORM2004_WindowHasParent(wnd){
4023    WriteToDebug("In SCORM2004_WindowHasParent");
4024    if ((wnd.parent != null) && (wnd.parent != wnd) && (typeof(wnd.parent) != "undefined")){
4025        WriteToDebug("Window Does Have Parent");
4026        return true;
4027    }
4028    else{
4029        WriteToDebug("Window Does Not Have Parent");
4030        return false;
4031    }
4032}
4033
4034
4035function SCORM2004_APIFound(obj){
4036    WriteToDebug("In SCORM2004_APIFound");
4037    if (obj == null || typeof(obj) == "undefined"){
4038        WriteToDebug("API NOT Found");
4039        return false;
4040    }
4041    else{
4042        WriteToDebug("API Found");
4043        return true;
4044    }
4045}
4046
4047var STANDARD = 'SCORM';
4048
4049var SCORM_LOGOUT = "logout";
4050var SCORM_SUSPEND = "suspend";
4051var SCORM_NORMAL_EXIT = "";
4052var SCORM_TIMEOUT = "time-out";
4053
4054var SCORM_PASSED = "passed";
4055var SCORM_FAILED = "failed";
4056var SCORM_COMPLETED = "completed";
4057var SCORM_BROWSED = "browsed";
4058var SCORM_INCOMPLETE = "incomplete";
4059var SCORM_NOT_ATTEMPTED = "not attempted";
4060
4061var SCORM_CREDIT = "credit";
4062var SCORM_NO_CREDIT = "no-credit";
4063
4064var SCORM_BROWSE = "browse";
4065var SCORM_NORMAL = "normal";
4066var SCORM_REVIEW = "review";
4067
4068var SCORM_ENTRY_ABINITIO = "ab-initio";
4069var SCORM_ENTRY_RESUME = "resume";
4070var SCORM_ENTRY_NORMAL = "";
4071
4072
4073var SCORM_TLA_EXIT_MESSAGE = "exit,message";
4074var SCORM_TLA_EXIT_NO_MESSAGE = "exit,no message";
4075var SCORM_TLA_CONTINUE_MESSAGE = "continue,message";
4076var SCORM_TLA_CONTINUE_NO_MESSAGE = "continue,no message";
4077
4078var SCORM_RESULT_CORRECT = "correct";
4079var SCORM_RESULT_WRONG = "wrong";
4080var SCORM_RESULT_UNANTICIPATED = "unanticipated";
4081var SCORM_RESULT_NEUTRAL = "neutral";
4082
4083var SCORM_INTERACTION_TYPE_TRUE_FALSE = "true-false";
4084var SCORM_INTERACTION_TYPE_CHOICE = "choice";
4085var SCORM_INTERACTION_FILL_IN = "fill-in";
4086var SCORM_INTERACTION_TYPE_MATCHING = "matching";
4087var SCORM_INTERACTION_TYPE_PERFORMANCE = "performance";
4088var SCORM_INTERACTION_TYPE_SEQUENCING = "sequencing";
4089var SCORM_INTERACTION_TYPE_LIKERT = "likert";
4090var SCORM_INTERACTION_TYPE_NUMERIC = "numeric";
4091
4092
4093var SCORM_NO_ERROR = "0";
4094var SCORM_ERROR_INVALID_PREFERENCE = "-1";
4095var SCORM_ERROR_INVALID_STATUS = "-2";
4096var SCORM_ERROR_INVALID_SPEED = "-3";
4097var SCORM_ERROR_INVALID_TIMESPAN = "-4";
4098var SCORM_ERROR_INVALID_TIME_LIMIT_ACTION = "-5";
4099var SCORM_ERROR_INVALID_DECIMAL = "-6";
4100var SCORM_ERROR_INVALID_CREDIT = "-7";
4101var SCORM_ERROR_INVALID_LESSON_MODE = "-8";
4102var SCORM_ERROR_INVALID_ENTRY = "-9";
4103
4104var SCORM_TRUE = "true";
4105var SCORM_FALSE = "false";
4106
4107var SCORM_findAPITries = 0;
4108var SCORM_objAPI = null;
4109
4110var intSCORMError = SCORM_NO_ERROR;
4111var strSCORMErrorString = "";
4112var strSCORMErrorDiagnostic = "";
4113
4114var blnReviewModeSoReadOnly = false;
4115
4116function SCORM_Initialize(){
4117
4118    var blnResult = true;
4119
4120    WriteToDebug("In SCORM_Initialize");
4121
4122    SCORM_ClearErrorInfo();
4123
4124    WriteToDebug("Grabbing API");
4125
4126    try{
4127        SCORM_objAPI = SCORM_GrabAPI();
4128    }
4129    catch (e){
4130        WriteToDebug("Error grabbing 1.2 API-" + e.name + ":" + e.message);
4131    }
4132
4133    if (typeof(SCORM_objAPI) == "undefined" || SCORM_objAPI == null){
4134        WriteToDebug("Unable to acquire SCORM API:")
4135        WriteToDebug("SCORM_objAPI=" + typeof(SCORM_objAPI));
4136
4137        InitializeExecuted(false, "Error - unable to acquire LMS API, content may not play properly and results may not be recorded.  Please contact technical support.");
4138        return false;
4139    }
4140
4141    WriteToDebug("Calling LMSInit");
4142
4143    blnResult = SCORM_CallLMSInitialize();
4144
4145    if (! blnResult){
4146        WriteToDebug("ERROR Initializing LMS");
4147
4148        InitializeExecuted(false, "Error initializing communications with LMS");
4149
4150        return false;
4151    }
4152
4153    //only reset status and such if we are not reviewing
4154    if (SCORM_GetLessonMode() != MODE_REVIEW){
4155
4156        if (SCORM_IsContentInBrowseMode()){
4157            WriteToDebug("Setting Status to Browsed");
4158            blnResult = SCORM_CallLMSSetValue("cmi.core.lesson_status", SCORM_BROWSED);
4159        }
4160        else{
4161            if (! PREVENT_STATUS_CHANGE_DURING_INIT) {
4162                //only set the status to incomplete if it's not attempted yet
4163                if (SCORM_GetStatus() == LESSON_STATUS_NOT_ATTEMPTED){
4164                    WriteToDebug("Setting Status to Incomplete");
4165                    blnResult = SCORM_CallLMSSetValue("cmi.core.lesson_status", SCORM_INCOMPLETE);
4166                }
4167            }
4168        }
4169
4170        //we want to set the exit type to suspend immediately because some LMS's only store data if they get a suspend request
4171        blnResult = SCORM_CallLMSSetValue("cmi.core.exit", SCORM_TranslateExitTypeToSCORM(DEFAULT_EXIT_TYPE)) && blnResult;
4172    }
4173    else{
4174        //mode is review, check if we should go to read only mode
4175        if (!(typeof(REVIEW_MODE_IS_READ_ONLY) == "undefined") && REVIEW_MODE_IS_READ_ONLY === true){
4176            blnReviewModeSoReadOnly = true;
4177        }
4178    }
4179
4180    WriteToDebug("Calling InitializeExecuted with parameter-" + blnResult);
4181
4182    InitializeExecuted(blnResult, "");
4183
4184    return;
4185}
4186
4187
4188
4189
4190function SCORM_Finish(strExitType, blnStatusWasSet){
4191
4192    var strStatusAfterCompletion;
4193    var blnResult = true;
4194
4195    WriteToDebug("In SCORM_Finish strExitType=" + strExitType + ", blnStatusWasSet=" + blnStatusWasSet);
4196
4197    SCORM_ClearErrorInfo();
4198
4199    if ( (strExitType == EXIT_TYPE_FINISH) && ! blnStatusWasSet ){
4200
4201        WriteToDebug("Getting completion status");
4202
4203        strStatusAfterCompletion = SCORM_GetCompletionStatus();
4204
4205        WriteToDebug("Setting completion status to " + strStatusAfterCompletion);
4206
4207        blnResult = SCORM_CallLMSSetValue("cmi.core.lesson_status", strStatusAfterCompletion) && blnResult;
4208    }
4209
4210    WriteToDebug("Setting Exit");
4211
4212    blnResult = SCORM_CallLMSSetValue("cmi.core.exit", SCORM_TranslateExitTypeToSCORM(strExitType)) && blnResult;
4213
4214    WriteToDebug("Calling Commit");
4215
4216    blnResult = SCORM_CallLMSCommit() && blnResult;
4217
4218    WriteToDebug("Calling Finish");
4219
4220    blnResult = SCORM_CallLMSFinish() && blnResult;
4221
4222    WriteToDebug("Returning " + blnResult);
4223
4224    return blnResult;
4225
4226}
4227
4228
4229function SCORM_CommitData(){
4230    WriteToDebug("In SCORM_CommitData");
4231    SCORM_ClearErrorInfo();
4232    return SCORM_CallLMSCommit();
4233}
4234
4235
4236
4237//---------------------------------------------------------------------------------
4238//General Get and Set Values
4239
4240function SCORM_GetStudentID(){
4241    WriteToDebug("In SCORM_GetStudentID");
4242    SCORM_ClearErrorInfo();
4243    return SCORM_CallLMSGetValue("cmi.core.student_id");
4244}
4245
4246function SCORM_GetStudentName(){
4247    WriteToDebug("In SCORM_GetStudentName");
4248    SCORM_ClearErrorInfo();
4249    return SCORM_CallLMSGetValue("cmi.core.student_name");
4250}
4251
4252function SCORM_GetBookmark(){
4253    WriteToDebug("In SCORM_GetBookmark");
4254    SCORM_ClearErrorInfo();
4255    return SCORM_CallLMSGetValue("cmi.core.lesson_location");
4256}
4257
4258function SCORM_SetBookmark(strBookmark){
4259    WriteToDebug("In SCORM_SetBookmark strBookmark=" + strBookmark);
4260    SCORM_ClearErrorInfo();
4261    return SCORM_CallLMSSetValue("cmi.core.lesson_location", strBookmark);
4262}
4263
4264function SCORM_GetDataChunk(){
4265    WriteToDebug("In SCORM_GetDataChunk");
4266    SCORM_ClearErrorInfo();
4267    return SCORM_CallLMSGetValue("cmi.suspend_data");
4268}
4269
4270function SCORM_SetDataChunk(strData){
4271    //need to check for character limits here 4096 characters
4272    WriteToDebug("In SCORM_SetDataChunk");
4273    SCORM_ClearErrorInfo();
4274
4275    if(USE_STRICT_SUSPEND_DATA_LIMITS==true) {
4276        if(strData.length > 4096) {
4277            WriteToDebug("SCORM_SetDataChunk - suspend_data too large (4096 character limit for SCORM 1.2)");
4278            return false;
4279        }else{
4280            return SCORM_CallLMSSetValue("cmi.suspend_data", strData);
4281        }
4282    }else{
4283        return SCORM_CallLMSSetValue("cmi.suspend_data", strData);
4284    }
4285}
4286
4287
4288function SCORM_GetLaunchData(){
4289    WriteToDebug("In SCORM_GetLaunchData");
4290    SCORM_ClearErrorInfo();
4291    return SCORM_CallLMSGetValue("cmi.launch_data");
4292}
4293
4294function SCORM_GetComments(){
4295    WriteToDebug("In SCORM_GetComments");
4296    SCORM_ClearErrorInfo();
4297    return SCORM_CallLMSGetValue("cmi.comments");
4298}
4299
4300function SCORM_WriteComment(strComment){
4301    WriteToDebug("In SCORM_WriteComment strComment=" + strComment);
4302    SCORM_ClearErrorInfo();
4303    return SCORM_CallLMSSetValue("cmi.comments", strComment);
4304}
4305
4306function SCORM_GetLMSComments(){
4307    WriteToDebug("In SCORM_GetLMSComments");
4308    SCORM_ClearErrorInfo();
4309    return SCORM_CallLMSGetValue("cmi.comments_from_lms");
4310}
4311
4312
4313function SCORM_GetAudioPlayPreference(){
4314
4315    var intTempPreference;
4316
4317    WriteToDebug("In SCORM_GetAudioPlayPreference");
4318
4319    SCORM_ClearErrorInfo();
4320
4321    intTempPreference = SCORM_CallLMSGetValue("cmi.student_preference.audio");
4322
4323    if (intTempPreference == ""){
4324        intTempPreference = 0;
4325    }
4326
4327    intTempPreference = parseInt(intTempPreference, 10);
4328
4329    WriteToDebug("intTempPreference=" + intTempPreference);
4330
4331    if (intTempPreference > 0){
4332        WriteToDebug("Returning On");
4333        return PREFERENCE_ON;
4334    }
4335    else if (intTempPreference == 0){
4336        WriteToDebug("Returning Default");
4337        return PREFERENCE_DEFAULT;
4338    }
4339    else if (intTempPreference < 0) {
4340        WriteToDebug("returning Off");
4341        return PREFERENCE_OFF;
4342    }
4343    else{
4344        WriteToDebug("Error: Invalid preference");
4345        SCORM_SetErrorInfoManually(SCORM_ERROR_INVALID_PREFERENCE,
4346                                  "Invalid audio preference received from LMS",
4347                                  "intTempPreference=" + intTempPreference);
4348        return null;
4349    }
4350}
4351
4352
4353
4354function SCORM_GetAudioVolumePreference(){
4355
4356    var intTempPreference;
4357
4358    WriteToDebug("In SCORM_GetAudioVollumePreference");
4359
4360    SCORM_ClearErrorInfo();
4361
4362    intTempPreference = SCORM_CallLMSGetValue("cmi.student_preference.audio");
4363
4364    WriteToDebug("intTempPreference=" + intTempPreference);
4365
4366    if (intTempPreference == "") {
4367        intTempPreference = 100;
4368    }
4369
4370    intTempPreference = parseInt(intTempPreference, 10);
4371
4372    if (intTempPreference <= 0){
4373        WriteToDebug("Setting to 100");
4374        intTempPreference = 100;
4375    }
4376
4377    if (! (intTempPreference > 0 && intTempPreference <= 100)){
4378        WriteToDebug("ERROR: invalid preference");
4379        SCORM_SetErrorInfoManually(SCORM_ERROR_INVALID_PREFERENCE,
4380                                  "Invalid audio preference received from LMS",
4381                                  "intTempPreference=" + intTempPreference);
4382        return null;
4383    }
4384
4385    WriteToDebug("Returning " + intTempPreference);
4386    return intTempPreference;
4387
4388}
4389
4390function SCORM_SetAudioPreference(PlayPreference, intPercentOfMaxVolume){
4391
4392    WriteToDebug("In SCORM_SetAudioPreference PlayPreference=" + PlayPreference + ", intPercentOfMaxVolume=" + intPercentOfMaxVolume);
4393
4394    SCORM_ClearErrorInfo();
4395
4396    if (PlayPreference == PREFERENCE_OFF){
4397        WriteToDebug("Setting percent to -1 - OFF");
4398        intPercentOfMaxVolume = -1;
4399    }
4400
4401    return SCORM_CallLMSSetValue("cmi.student_preference.audio", intPercentOfMaxVolume);
4402}
4403
4404
4405
4406function SCORM_SetLanguagePreference(strLanguage){
4407    WriteToDebug("In SCORM_SetLanguagePreference strLanguage=" + strLanguage);
4408    SCORM_ClearErrorInfo();
4409    return SCORM_CallLMSSetValue("cmi.student_preference.language", strLanguage);
4410
4411}
4412
4413
4414function SCORM_GetLanguagePreference(){
4415    WriteToDebug("In SCORM_GetLanguagePreference");
4416    SCORM_ClearErrorInfo();
4417    return SCORM_CallLMSGetValue("cmi.student_preference.language");
4418
4419}
4420
4421
4422function SCORM_SetSpeedPreference(intPercentOfMax){
4423
4424    var intSCORMSpeed;  //SCORM's scale is -100 to +100, our range is 1 to 100
4425
4426    WriteToDebug("In SCORM_SetSpeedPreference intPercentOfMax=" + intPercentOfMax);
4427
4428    SCORM_ClearErrorInfo();
4429
4430    intSCORMSpeed = (intPercentOfMax * 2) - 100;
4431
4432    WriteToDebug("intSCORMSpeed=" + intSCORMSpeed);
4433
4434    return SCORM_CallLMSSetValue("cmi.student_preference.speed", intSCORMSpeed);
4435
4436}
4437
4438
4439function SCORM_GetSpeedPreference(){
4440
4441    var intSCORMSpeed;
4442    var intPercentOfMax;
4443
4444    WriteToDebug("In SCORM_GetSpeedPreference");
4445
4446    SCORM_ClearErrorInfo();
4447
4448    intSCORMSpeed = SCORM_CallLMSGetValue("cmi.student_preference.speed");
4449
4450    WriteToDebug("intSCORMSpeed=" + intSCORMSpeed);
4451
4452    if (intSCORMSpeed == ""){
4453        WriteToDebug("Detected empty string, defaulting to 100");
4454        intSCORMSpeed = 100;
4455    }
4456
4457    if ( ! ValidInteger(intSCORMSpeed) ){
4458        WriteToDebug("ERROR - invalid integer");
4459        SCORM_SetErrorInfoManually(SCORM_ERROR_INVALID_SPEED,
4460                                  "Invalid speed preference received from LMS - not an integer",
4461                                  "intSCORMSpeed=" + intSCORMSpeed);
4462        return null;
4463    }
4464
4465    intSCORMSpeed = parseInt(intSCORMSpeed, 10);
4466
4467    if (intSCORMSpeed < -100 || intSCORMSpeed > 100){
4468        WriteToDebug("ERROR - out of range");
4469        SCORM_SetErrorInfoManually(SCORM_ERROR_INVALID_SPEED,
4470                                  "Invalid speed preference received from LMS - out of range",
4471                                  "intSCORMSpeed=" + intSCORMSpeed);
4472        return null;
4473    }
4474
4475    intPercentOfMax = (intSCORMSpeed + 100) / 2;
4476    intPercentOfMax = parseInt(intPercentOfMax, 10);
4477
4478    WriteToDebug("Returning " + intPercentOfMax);
4479
4480    return intPercentOfMax;
4481
4482}
4483
4484
4485
4486
4487function SCORM_SetTextPreference(intPreference){
4488    WriteToDebug("In SCORM_SetTextPreference intPreference=" + intPreference);
4489
4490    SCORM_ClearErrorInfo();
4491
4492    return SCORM_CallLMSSetValue("cmi.student_preference.text", intPreference);
4493}
4494
4495
4496function SCORM_GetTextPreference(){
4497    var intTempPreference;
4498
4499    WriteToDebug("In SCORM_GetTextPreference");
4500
4501    SCORM_ClearErrorInfo();
4502
4503    intTempPreference = SCORM_CallLMSGetValue("cmi.student_preference.text");
4504    intTempPreference = parseInt(intTempPreference, 10);
4505
4506    WriteToDebug("intTempPreference=" + intTempPreference);
4507
4508    if (intTempPreference > 0){
4509        WriteToDebug("Returning On");
4510        return PREFERENCE_ON;
4511    }
4512    else if (intTempPreference == 0 || intTempPreference == ""){
4513        WriteToDebug("Returning Default");
4514        return PREFERENCE_DEFAULT;
4515    }
4516    else if (intTempPreference < 0) {
4517        WriteToDebug("returning Off");
4518        return PREFERENCE_OFF;
4519    }
4520    else{
4521        WriteToDebug("Error: Invalid preference");
4522        SCORM_SetErrorInfoManually(SCORM_ERROR_INVALID_PREFERENCE,
4523                                  "Invalid text preference received from LMS",
4524                                  "intTempPreference=" + intTempPreference);
4525        return null;
4526    }
4527}
4528
4529
4530
4531//---------------------------------------------------------------------------------
4532//Time Management Functions
4533function SCORM_GetPreviouslyAccumulatedTime(){
4534
4535    var strCMITime;
4536    var intMilliseconds;
4537
4538    WriteToDebug("In SCORM_GetPreviouslyAccumulatedTime");
4539
4540    SCORM_ClearErrorInfo();
4541
4542    strCMITime = SCORM_CallLMSGetValue("cmi.core.total_time")
4543
4544    WriteToDebug("strCMITime=" + strCMITime);
4545
4546    if (! IsValidCMITimeSpan(strCMITime)){
4547        WriteToDebug("ERROR - Invalid CMITimeSpan");
4548        SCORM_SetErrorInfoManually(SCORM_ERROR_INVALID_TIMESPAN,
4549                                    "Invalid timespan received from LMS",
4550                                    "strTime=" + strCMITime);
4551        return null;
4552    }
4553
4554    intMilliseconds = ConvertCMITimeSpanToMS(strCMITime);
4555
4556    WriteToDebug("Returning " + intMilliseconds);
4557
4558    return intMilliseconds;
4559
4560}
4561
4562
4563function SCORM_SaveTime(intMilliSeconds){
4564
4565    var strCMITime;
4566
4567    WriteToDebug("In SCORM_SaveTime intMilliSeconds=" + intMilliSeconds);
4568
4569    SCORM_ClearErrorInfo();
4570
4571    strCMITime = ConvertMilliSecondsToSCORMTime(intMilliSeconds, true);
4572
4573    WriteToDebug("strCMITime=" + strCMITime);
4574
4575    return SCORM_CallLMSSetValue("cmi.core.session_time", strCMITime);
4576}
4577
4578function SCORM_GetMaxTimeAllowed(){
4579
4580    var strCMITime;
4581    var intMilliseconds;
4582
4583    WriteToDebug("In SCORM_GetMaxTimeAllowed");
4584
4585    SCORM_ClearErrorInfo();
4586
4587    strCMITime = SCORM_CallLMSGetValue("cmi.student_data.max_time_allowed")
4588
4589    WriteToDebug("strCMITime=" + strCMITime);
4590
4591    if (strCMITime == ""){
4592        strCMITime = "9999:99:99.99";
4593    }
4594
4595    if (! IsValidCMITimeSpan(strCMITime)){
4596        WriteToDebug("ERROR - Invalid CMITimeSpan");
4597        SCORM_SetErrorInfoManually(SCORM_ERROR_INVALID_TIMESPAN,
4598                                    "Invalid timespan received from LMS",
4599                                    "strTime=" + strCMITime);
4600        return null;
4601    }
4602
4603    intMilliseconds = ConvertCMITimeSpanToMS(strCMITime);
4604
4605    WriteToDebug("intMilliseconds=" + intMilliseconds);
4606
4607    return intMilliseconds;
4608}
4609
4610
4611
4612function SCORM_DisplayMessageOnTimeout(){
4613
4614    var strTLA;
4615
4616    SCORM_ClearErrorInfo();
4617
4618    WriteToDebug("In SCORM_DisplayMessageOnTimeout");
4619
4620    strTLA = SCORM_CallLMSGetValue("cmi.student_data.time_limit_action");
4621
4622    WriteToDebug("strTLA=" + strTLA);
4623
4624    if (strTLA == SCORM_TLA_EXIT_MESSAGE || strTLA == SCORM_TLA_CONTINUE_MESSAGE){
4625        WriteToDebug("returning true");
4626        return true;
4627    }
4628    else if(strTLA == SCORM_TLA_EXIT_NO_MESSAGE || strTLA == SCORM_TLA_CONTINUE_NO_MESSAGE || strTLA == ""){
4629        WriteToDebug("returning false");
4630        return false;
4631    }
4632    else{
4633        WriteToDebug("Error invalid TLA");
4634        SCORM_SetErrorInfoManually(SCORM_ERROR_INVALID_TIME_LIMIT_ACTION,
4635                                  "Invalid time limit action received from LMS",
4636                                  "strTLA=" + strTLA);
4637
4638        return null;
4639    }
4640
4641}
4642
4643function SCORM_ExitOnTimeout(){
4644
4645    var strTLA;
4646
4647    WriteToDebug("In SCORM_ExitOnTimeout");
4648
4649    SCORM_ClearErrorInfo();
4650
4651    strTLA = SCORM_CallLMSGetValue("cmi.student_data.time_limit_action");
4652
4653    WriteToDebug("strTLA=" + strTLA);
4654
4655    if (strTLA == SCORM_TLA_EXIT_MESSAGE || strTLA == SCORM_TLA_EXIT_NO_MESSAGE){
4656        WriteToDebug("returning true");
4657        return true;
4658    }
4659    else if(strTLA == SCORM_TLA_CONTINUE_MESSAGE || strTLA == SCORM_TLA_CONTINUE_NO_MESSAGE || strTLA == ""){
4660        WriteToDebug("returning false");
4661        return false;
4662    }
4663    else{
4664        WriteToDebug("ERROR invalid TLA");
4665        SCORM_SetErrorInfoManually(SCORM_ERROR_INVALID_TIME_LIMIT_ACTION,
4666                                  "Invalid time limit action received from LMS",
4667                                  "strTLA=" + strTLA);
4668
4669        return null;
4670    }
4671
4672}
4673
4674
4675function SCORM_GetPassingScore(){
4676    var fltScore;
4677
4678    WriteToDebug("In SCORM_GetPassingScore");
4679
4680    SCORM_ClearErrorInfo();
4681
4682    fltScore = SCORM_CallLMSGetValue("cmi.student_data.mastery_score")
4683
4684    WriteToDebug("fltScore=" + fltScore);
4685
4686    if (fltScore == ""){
4687        fltScore = 0;
4688    }
4689
4690    if ( ! IsValidDecimal(fltScore)){
4691        WriteToDebug("Error - score is not a valid decimal");
4692        SCORM_SetErrorInfoManually(SCORM_ERROR_INVALID_DECIMAL,
4693                                  "Invalid mastery score received from LMS",
4694                                  "fltScore=" + fltScore);
4695        return null;
4696    }
4697
4698    fltScore = parseFloat(fltScore);
4699
4700    WriteToDebug("returning fltScore");
4701
4702    return fltScore;
4703}
4704
4705
4706function SCORM_SetScore(intScore, intMaxScore, intMinScore){
4707
4708    var blnResult;
4709    intScore = RoundToPrecision(intScore, 7);
4710    intMaxScore = RoundToPrecision(intMaxScore, 7);
4711    intMinScore = RoundToPrecision(intMinScore, 7);
4712
4713    WriteToDebug("In SCORM_SetScore intScore=" + intScore + ", intMaxScore=" + intMaxScore + ", intMinScore=" + intMinScore);
4714
4715    SCORM_ClearErrorInfo();
4716
4717    blnResult = SCORM_CallLMSSetValue("cmi.core.score.raw", intScore);
4718    blnResult = SCORM_CallLMSSetValue("cmi.core.score.max", intMaxScore) && blnResult;
4719    blnResult = SCORM_CallLMSSetValue("cmi.core.score.min", intMinScore) && blnResult;
4720
4721    WriteToDebug("Returning " + blnResult);
4722
4723    return blnResult;
4724}
4725
4726function SCORM_GetScore(){
4727
4728
4729    WriteToDebug("In SCORM_GetScore");
4730
4731    SCORM_ClearErrorInfo();
4732
4733    return SCORM_CallLMSGetValue("cmi.core.score.raw");
4734
4735}
4736
4737function SCORM_SetPointBasedScore(intScore, intMaxScore, intMinScore){
4738    WriteToDebug("SCORM_SetPointBasedScore - SCORM 1.1 and 1.2 do not support SetPointBasedScore, falling back to SetScore");
4739    return SCORM_SetScore(intScore, intMaxScore, intMinScore);
4740}
4741
4742function SCORM_GetScaledScore(intScore, intMaxScore, intMinScore){
4743    WriteToDebug("SCORM_GetScaledScore - SCORM 1.1 and 1.2 do not support GetScaledScore, returning false");
4744    return false;
4745}
4746
4747function SCORM_RecordInteraction(strID, strResponse, blnCorrect, strCorrectResponse, strDescription,
4748                                intWeighting, intLatency, strLearningObjectiveID, dtmTime, scormInteractionType,
4749                                strAlternateResponse, strAlternateCorrectResponse){
4750
4751    var blnResult;
4752    var blnTempResult;
4753    var intInteractionIndex;
4754    var strResult;
4755
4756
4757    SCORM_ClearErrorInfo();
4758
4759    //In SCORM 1.2, we always want to add a new interaction rather than updating an old interaction.
4760    //This is because some LMS vendors have mis-interpreted the "write only" stipulation on interactions to mean "write once"
4761    intInteractionIndex = SCORM_CallLMSGetValue("cmi.interactions._count");
4762
4763    WriteToDebug("intInteractionIndex=" + intInteractionIndex);
4764
4765    if (intInteractionIndex == ""){
4766        WriteToDebug("Setting Interaction Index to 0");
4767        intInteractionIndex = 0;
4768    }
4769
4770    //need to leave support for blnCorrect=t/f for legacy implementations of RSECA
4771    if (IsNumeric(blnCorrect)) {
4772    	strResult = blnCorrect;
4773    }
4774    else {
4775		if (blnCorrect == true || blnCorrect == INTERACTION_RESULT_CORRECT){
4776			strResult = SCORM_RESULT_CORRECT;
4777		}
4778		else if (blnCorrect == "" || blnCorrect == "false" || blnCorrect == INTERACTION_RESULT_WRONG){  //compare against the string "false" because ("" == false) evaluates to true
4779			strResult = SCORM_RESULT_WRONG;
4780		}
4781		else if (blnCorrect == INTERACTION_RESULT_UNANTICIPATED){
4782			strResult = SCORM_RESULT_UNANTICIPATED;
4783		}
4784		else if (blnCorrect == INTERACTION_RESULT_NEUTRAL){
4785			strResult = SCORM_RESULT_NEUTRAL;
4786		}
4787	}
4788
4789    WriteToDebug("strResult=" + strResult);
4790
4791    blnResult = SCORM_CallLMSSetValue("cmi.interactions." + intInteractionIndex + ".id", strID);
4792    blnResult = SCORM_CallLMSSetValue("cmi.interactions." + intInteractionIndex + ".type", scormInteractionType) && blnResult;
4793
4794
4795    //try to save the data using the verbose description of the interaciton results (this is not strictly conformant, but most LMS's will allow it and it still passes the Test Suite
4796    //if the long version errs, try again with the short version (strict adherence to the standard) - this applies to response and correct response
4797
4798    if (strResponse !== null) {
4799        blnTempResult = SCORM_CallLMSSetValue("cmi.interactions." + intInteractionIndex + ".student_response", strResponse);
4800
4801        if (blnTempResult == false && strAlternateResponse !== null){
4802            blnTempResult = SCORM_CallLMSSetValue("cmi.interactions." + intInteractionIndex + ".student_response", strAlternateResponse);
4803
4804            //
4805            // by trying the long version and then falling back to the short version there may have
4806            // been an error set that should be cleared if the short version works, unfortunately
4807            // this obscures any error in the first two calls, but any error in those calls would
4808            // have already been obscured by trying a long version that failed, therefore it was
4809            // determined that it was better to go ahead and clear the error because that particular
4810            // piece of data (the response) was actually captured, and any of the calls after this
4811            // point could still have a proper error *and* that the first two calls are the ones
4812            // most likely to succeed and if they don't then things are really broken
4813            //
4814            if (blnTempResult == true) {
4815                SCORM_ClearErrorInfo();
4816            }
4817        }
4818    }
4819
4820    blnResult = blnResult && blnTempResult;
4821
4822    if (strCorrectResponse != undefined && strCorrectResponse != null && strCorrectResponse != ""){
4823
4824        blnTempResult = SCORM_CallLMSSetValue("cmi.interactions." + intInteractionIndex + ".correct_responses.0.pattern", strCorrectResponse);
4825        if (blnTempResult == false){
4826            blnTempResult = SCORM_CallLMSSetValue("cmi.interactions." + intInteractionIndex + ".correct_responses.0.pattern", strAlternateCorrectResponse);
4827
4828            // see comment above
4829            if (blnTempResult == true) {
4830                SCORM_ClearErrorInfo();
4831            }
4832        }
4833
4834        blnResult = blnResult && blnTempResult;
4835    }
4836
4837    if (strResult != undefined && strResult != null && strResult != ""){
4838        blnResult = SCORM_CallLMSSetValue("cmi.interactions." + intInteractionIndex + ".result", strResult) && blnResult;
4839    }
4840
4841    //ignore the description parameter in SCORM 1.2, there is nothing we can do with it
4842
4843    if (intWeighting != undefined && intWeighting != null && intWeighting != ""){
4844        blnResult = SCORM_CallLMSSetValue("cmi.interactions." + intInteractionIndex + ".weighting", intWeighting) && blnResult;
4845    }
4846
4847    if (intLatency != undefined && intLatency != null && intLatency != ""){
4848        blnResult = SCORM_CallLMSSetValue("cmi.interactions." + intInteractionIndex + ".latency", ConvertMilliSecondsToSCORMTime(intLatency, true)) && blnResult;
4849    }
4850
4851    if (strLearningObjectiveID != undefined && strLearningObjectiveID != null && strLearningObjectiveID != ""){
4852        blnResult = SCORM_CallLMSSetValue("cmi.interactions." + intInteractionIndex + ".objectives.0.id", strLearningObjectiveID) && blnResult;
4853    }
4854
4855    blnResult = SCORM_CallLMSSetValue("cmi.interactions." + intInteractionIndex + ".time", ConvertDateToCMITime(dtmTime)) && blnResult;
4856
4857    WriteToDebug("Returning " + blnResult);
4858
4859    return blnResult;
4860}
4861
4862function SCORM_RecordTrueFalseInteraction(strID, blnResponse, blnCorrect, blnCorrectResponse, strDescription, intWeighting, intLatency, strLearningObjectiveID, dtmTime){
4863
4864    WriteToDebug("In SCORM_RecordTrueFalseInteraction strID=" + strID + ", strResponse=" + strResponse +
4865                    ", blnCorrect=" + blnCorrect + ", strCorrectResponse=" + strCorrectResponse +
4866                    ", strDescription=" + strDescription + ", intWeighting=" + intWeighting + ", intLatency=" + intLatency +
4867                    ", strLearningObjectiveID=" + strLearningObjectiveID + ", dtmTime=" + dtmTime);
4868
4869    var strResponse = null;
4870    var strCorrectResponse = null;
4871
4872    if (blnResponse == true){
4873        strResponse = "t";
4874    }
4875    else if (blnResponse !== null) {
4876        strResponse = "f";
4877    }
4878
4879    if (blnCorrectResponse == true){
4880        strCorrectResponse = "t";
4881    }
4882    else if(blnCorrectResponse == false){       //test for false b/c it could be null in which case we want to leave it as `null`
4883        strCorrectResponse = "f";
4884    }
4885
4886    return SCORM_RecordInteraction(strID, strResponse, blnCorrect, strCorrectResponse, strDescription,
4887                                   intWeighting, intLatency, strLearningObjectiveID, dtmTime, SCORM_INTERACTION_TYPE_TRUE_FALSE,
4888                                   strResponse, strCorrectResponse);
4889}
4890
4891function SCORM_RecordMultipleChoiceInteraction(strID, aryResponse, blnCorrect, aryCorrectResponse, strDescription, intWeighting, intLatency, strLearningObjectiveID, dtmTime){
4892
4893    WriteToDebug("In SCORM_RecordMultipleChoiceInteraction strID=" + strID + ", aryResponse=" + aryResponse +
4894                    ", blnCorrect=" + blnCorrect + ", aryCorrectResponse=" + aryCorrectResponse +
4895                    ", strDescription=" + strDescription + ", intWeighting=" + intWeighting + ", intLatency=" + intLatency +
4896                    ", strLearningObjectiveID=" + strLearningObjectiveID + ", dtmTime=" + dtmTime);
4897
4898
4899    var strResponse = null;
4900    var strResponseLong = null;
4901
4902    var strCorrectResponse = "";
4903    var strCorrectResponseLong = "";
4904
4905    if (aryResponse !== null) {
4906        strResponse = "";
4907        strResponseLong = "";
4908
4909        for (var i=0; i < aryResponse.length; i++){
4910
4911            if (strResponse.length > 0) {strResponse += ",";}
4912            if (strResponseLong.length > 0) {strResponseLong += ",";}
4913
4914            strResponse += aryResponse[i].Short;
4915            strResponseLong += aryResponse[i].Long;
4916        }
4917    }
4918
4919    for (var i=0; i < aryCorrectResponse.length; i++){
4920
4921        if (strCorrectResponse.length > 0) {strCorrectResponse += ",";}
4922        if (strCorrectResponseLong.length > 0) {strCorrectResponseLong += ",";}
4923
4924        strCorrectResponse += aryCorrectResponse[i].Short;
4925        strCorrectResponseLong += aryCorrectResponse[i].Long;
4926    }
4927
4928    var blnSuccessfullySaved;
4929
4930    blnSuccessfullySaved = SCORM_RecordInteraction(strID, strResponseLong, blnCorrect, strCorrectResponseLong, strDescription,
4931                                                   intWeighting, intLatency, strLearningObjectiveID, dtmTime, SCORM_INTERACTION_TYPE_CHOICE,
4932                                                   strResponse, strCorrectResponse);
4933
4934    return blnSuccessfullySaved;
4935
4936}
4937
4938
4939function SCORM_RecordFillInInteraction(strID, strResponse, blnCorrect, strCorrectResponse, strDescription, intWeighting, intLatency, strLearningObjectiveID, dtmTime){
4940
4941    WriteToDebug("In SCORM_RecordFillInInteraction strID=" + strID + ", strResponse=" + strResponse +
4942                    ", blnCorrect=" + blnCorrect + ", strCorrectResponse=" + strCorrectResponse +
4943                    ", strDescription=" + strDescription + ", intWeighting=" + intWeighting + ", intLatency=" + intLatency +
4944                    ", strLearningObjectiveID=" + strLearningObjectiveID + ", dtmTime=" + dtmTime);
4945
4946
4947    strResponse = new String(strResponse);
4948    if (strResponse.length > 255){strResponse = strResponse.substr(0, 255);}
4949
4950    if (strCorrectResponse == null){
4951        strCorrectResponse = "";
4952    }
4953
4954    strCorrectResponse = new String(strCorrectResponse);
4955    if (strCorrectResponse.length > 255){strCorrectResponse = strCorrectResponse.substr(0, 255);}
4956
4957    return SCORM_RecordInteraction(strID, strResponse, blnCorrect, strCorrectResponse, strDescription,
4958                                   intWeighting, intLatency, strLearningObjectiveID, dtmTime, SCORM_INTERACTION_FILL_IN,
4959                                   strResponse, strCorrectResponse);
4960}
4961
4962function SCORM_RecordMatchingInteraction(strID, aryResponse, blnCorrect, aryCorrectResponse, strDescription, intWeighting, intLatency, strLearningObjectiveID, dtmTime){
4963
4964    WriteToDebug("In SCORM_RecordMatchingInteraction strID=" + strID + ", aryResponse=" + aryResponse +
4965                    ", blnCorrect=" + blnCorrect + ", aryCorrectResponse=" + aryCorrectResponse +
4966                    ", strDescription=" + strDescription + ", intWeighting=" + intWeighting + ", intLatency=" + intLatency +
4967                    ", strLearningObjectiveID=" + strLearningObjectiveID + ", dtmTime=" + dtmTime);
4968
4969    var strResponse = null;
4970    var strResponseLong = null;
4971
4972    var strCorrectResponse = "";
4973    var strCorrectResponseLong = "";
4974
4975    if (aryResponse !== null) {
4976        strResponse = "";
4977        strResponseLong = "";
4978
4979        for (var i=0; i < aryResponse.length; i++){
4980
4981            if (strResponse.length > 0) {strResponse += ",";}
4982            if (strResponseLong.length > 0) {strResponseLong += ",";}
4983
4984            strResponse += aryResponse[i].Source.Short + "." + aryResponse[i].Target.Short;
4985            strResponseLong += aryResponse[i].Source.Long + "." + aryResponse[i].Target.Long;
4986        }
4987    }
4988
4989    for (var i=0; i < aryCorrectResponse.length; i++){
4990
4991        if (strCorrectResponse.length > 0) {strCorrectResponse += ",";}
4992        if (strCorrectResponseLong.length > 0) {strCorrectResponseLong += ",";}
4993
4994        strCorrectResponse += aryCorrectResponse[i].Source.Short + "." + aryCorrectResponse[i].Target.Short;
4995        strCorrectResponseLong += aryCorrectResponse[i].Source.Long + "." + aryCorrectResponse[i].Target.Long;
4996    }
4997
4998    var blnSuccessfullySaved;
4999
5000    blnSuccessfullySaved = SCORM_RecordInteraction(strID, strResponseLong, blnCorrect, strCorrectResponseLong, strDescription,
5001                                                   intWeighting, intLatency, strLearningObjectiveID, dtmTime, SCORM_INTERACTION_TYPE_MATCHING,
5002                                                   strResponse, strCorrectResponse);
5003
5004
5005    return blnSuccessfullySaved;
5006
5007}
5008
5009function SCORM_RecordPerformanceInteraction(strID, strResponse, blnCorrect, strCorrectResponse, strDescription, intWeighting, intLatency, strLearningObjectiveID, dtmTime){
5010
5011    WriteToDebug("In SCORM_RecordPerformanceInteraction strID=" + strID + ", strResponse=" + strResponse +
5012                    ", blnCorrect=" + blnCorrect + ", strCorrectResponse=" + strCorrectResponse +
5013                    ", strDescription=" + strDescription + ", intWeighting=" + intWeighting + ", intLatency=" + intLatency +
5014                    ", strLearningObjectiveID=" + strLearningObjectiveID + ", dtmTime=" + dtmTime);
5015
5016    if (strResponse !== null) {
5017        strResponse = new String(strResponse);
5018        if (strResponse.length > 255){strResponse = strResponse.substr(0, 255);}
5019    }
5020
5021    if (strCorrectResponse == null){
5022        strCorrectResponse = "";
5023    }
5024
5025    strCorrectResponse = new String(strCorrectResponse);
5026    if (strCorrectResponse.length > 255){strCorrectResponse = strCorrectResponse.substr(0, 255);}
5027
5028    return SCORM_RecordInteraction(strID, strResponse, blnCorrect, strCorrectResponse, strDescription,
5029                                   intWeighting, intLatency, strLearningObjectiveID, dtmTime, SCORM_INTERACTION_TYPE_PERFORMANCE,
5030                                   strResponse, strCorrectResponse);
5031}
5032
5033function SCORM_RecordSequencingInteraction(strID, aryResponse, blnCorrect, aryCorrectResponse, strDescription, intWeighting, intLatency, strLearningObjectiveID, dtmTime){
5034
5035    WriteToDebug("In SCORM_RecordSequencingInteraction strID=" + strID + ", aryResponse=" + aryResponse +
5036                    ", blnCorrect=" + blnCorrect + ", aryCorrectResponse=" + aryCorrectResponse +
5037                    ", strDescription=" + strDescription + ", intWeighting=" + intWeighting + ", intLatency=" + intLatency +
5038                    ", strLearningObjectiveID=" + strLearningObjectiveID + ", dtmTime=" + dtmTime);
5039
5040
5041    var strResponse = null;
5042    var strResponseLong = null;
5043
5044    var strCorrectResponse = "";
5045    var strCorrectResponseLong = "";
5046
5047    if (aryResponse !== null) {
5048        strResponse = "";
5049        strResponseLong = "";
5050
5051        for (var i=0; i < aryResponse.length; i++){
5052
5053            if (strResponse.length > 0) {strResponse += ",";}
5054            if (strResponseLong.length > 0) {strResponseLong += ",";}
5055
5056            strResponse += aryResponse[i].Short;
5057            strResponseLong += aryResponse[i].Long;
5058        }
5059    }
5060
5061    for (var i=0; i < aryCorrectResponse.length; i++){
5062
5063        if (strCorrectResponse.length > 0) {strCorrectResponse += ",";}
5064        if (strCorrectResponseLong.length > 0) {strCorrectResponseLong += ",";}
5065
5066        strCorrectResponse += aryCorrectResponse[i].Short;
5067        strCorrectResponseLong += aryCorrectResponse[i].Long;
5068    }
5069
5070    var blnSuccessfullySaved;
5071
5072    blnSuccessfullySaved = SCORM_RecordInteraction(strID, strResponseLong, blnCorrect, strCorrectResponseLong, strDescription,
5073                                                   intWeighting, intLatency, strLearningObjectiveID, dtmTime, SCORM_INTERACTION_TYPE_SEQUENCING,
5074                                                   strResponse, strCorrectResponse);
5075
5076
5077    return blnSuccessfullySaved;
5078
5079}
5080
5081function SCORM_RecordLikertInteraction(strID, response, blnCorrect, correctResponse, strDescription, intWeighting, intLatency, strLearningObjectiveID, dtmTime){
5082
5083    WriteToDebug("In SCORM_RecordLikertInteraction strID=" + strID + ", response=" + response +
5084                    ", blnCorrect=" + blnCorrect + ", correctResponse=" + correctResponse +
5085                    ", strDescription=" + strDescription + ", intWeighting=" + intWeighting + ", intLatency=" + intLatency +
5086                    ", strLearningObjectiveID=" + strLearningObjectiveID + ", dtmTime=" + dtmTime);
5087
5088    var strResponse = null;
5089    var strResponseLong = null;
5090
5091    var strCorrectResponse = "";
5092    var strCorrectResponseLong = "";
5093
5094    if (response !== null) {
5095        strResponse = response.Short;
5096        strResponseLong = response.Long;
5097    }
5098
5099    if (correctResponse != null){
5100        strCorrectResponse = correctResponse.Short;
5101        strCorrectResponseLong = correctResponse.Long;
5102    }
5103
5104    var blnSuccessfullySaved;
5105
5106    blnSuccessfullySaved = SCORM_RecordInteraction(strID, strResponseLong, blnCorrect, strCorrectResponseLong, strDescription,
5107                                                   intWeighting, intLatency, strLearningObjectiveID, dtmTime, SCORM_INTERACTION_TYPE_LIKERT,
5108                                                   strResponse, strCorrectResponse);
5109
5110    return blnSuccessfullySaved;
5111}
5112
5113function SCORM_RecordNumericInteraction(strID, strResponse, blnCorrect, strCorrectResponse, strDescription, intWeighting, intLatency, strLearningObjectiveID, dtmTime){
5114
5115    WriteToDebug("In SCORM_RecordNumericInteraction strID=" + strID + ", strResponse=" + strResponse +
5116                    ", blnCorrect=" + blnCorrect + ", strCorrectResponse=" + strCorrectResponse +
5117                    ", strDescription=" + strDescription + ", intWeighting=" + intWeighting + ", intLatency=" + intLatency +
5118                    ", strLearningObjectiveID=" + strLearningObjectiveID + ", dtmTime=" + dtmTime);
5119
5120    if (strCorrectResponse != undefined && strCorrectResponse != null) {
5121        if (IsValidDecimalRange(strCorrectResponse))
5122        {
5123            //we could receive a decimal range correct response from SCORM 2004/Tin Can/Cmi5. If we do we need to convert that
5124            //to a decimal for use in SCORM 1.2.
5125            strCorrectResponse = ConvertDecimalRangeToDecimalBasedOnLearnerResponse(strCorrectResponse, strResponse, blnCorrect);
5126        }
5127
5128        //SCORM 1.2 does not accept decimal range correct answers, so if it's not a decimal by this point we fail
5129        if (!IsValidDecimal(strCorrectResponse)){
5130            WriteToDebug("Returning False - SCORM_RecordNumericInteraction received invali
5130d correct response (not a decimal), strCorrectResponse=" +strCorrectResponse);
5131            return false;
5132        }
5133    }
5134
5135    return SCORM_RecordInteraction(strID, strResponse, blnCorrect, strCorrectResponse, strDescription,
5136                                   intWeighting, intLatency, strLearningObjectiveID, dtmTime, SCORM_INTERACTION_TYPE_NUMERIC,
5137                                   strResponse, strCorrectResponse);
5138}
5139
5140
5141function SCORM_GetEntryMode(){
5142    var strEntry;
5143
5144    WriteToDebug("In SCORM_GetEntryMode");
5145
5146    SCORM_ClearErrorInfo();
5147
5148    strEntry = SCORM_CallLMSGetValue("cmi.core.entry");
5149
5150    WriteToDebug("strEntry=" + strEntry);
5151
5152    if (strEntry == SCORM_ENTRY_ABINITIO){
5153        WriteToDebug("Returning first time");
5154        return ENTRY_FIRST_TIME;
5155    }
5156    else if (strEntry == SCORM_ENTRY_RESUME){
5157        WriteToDebug("Returning resume");
5158        return ENTRY_RESUME;
5159    }
5160    else if (strEntry == SCORM_ENTRY_NORMAL){
5161        WriteToDebug("returning normal");
5162        return ENTRY_REVIEW;
5163    }
5164    else{
5165        WriteToDebug("ERROR - invalide entry mode");
5166        SCORM_SetErrorInfoManually(SCORM_ERROR_INVALID_ENTRY,
5167                        "Invalid entry vocab received from LMS",
5168                        "strEntry=" + strEntry);
5169        return null;
5170    }
5171
5172}
5173
5174function SCORM_GetLessonMode(){
5175
5176    var strLessonMode;
5177
5178    WriteToDebug("In SCORM_GetLessonMode");
5179
5180    SCORM_ClearErrorInfo();
5181
5182    strLessonMode = SCORM_CallLMSGetValue("cmi.core.lesson_mode");
5183
5184    WriteToDebug("strLessonMode=" + strLessonMode);
5185
5186    if (strLessonMode == SCORM_BROWSE){
5187        WriteToDebug("Returning browse");
5188        return MODE_BROWSE;
5189    }
5190    else if(strLessonMode == SCORM_NORMAL){
5191        WriteToDebug("returning normal");
5192        return MODE_NORMAL;
5193    }
5194    else if(strLessonMode == SCORM_REVIEW){
5195        WriteToDebug("Returning Review");
5196        return MODE_REVIEW;
5197    }
5198    else{
5199        WriteToDebug("ERROR - invalid lesson mode");
5200        SCORM_SetErrorInfoManually(SCORM_ERROR_INVALID_LESSON_MODE,
5201                                "Invalid lesson_mode vocab received from LMS",
5202                                "strLessonMode=" + strLessonMode);
5203        return null;
5204    }
5205
5206}
5207
5208function SCORM_GetTakingForCredit(){
5209
5210    var strCredit;
5211
5212    WriteToDebug("In SCORM_GetTakingForCredit");
5213
5214    SCORM_ClearErrorInfo();
5215
5216    strCredit = SCORM_CallLMSGetValue("cmi.core.credit");
5217
5218    WriteToDebug("strCredit=" + strCredit);
5219
5220    if (strCredit == "credit"){
5221        WriteToDebug("Returning true");
5222        return true;
5223    }
5224    else if (strCredit == "no-credit"){
5225        WriteToDebug("Returning false");
5226        return false;
5227    }
5228    else{
5229        WriteToDebug("ERROR - invalid credit");
5230        SCORM_SetErrorInfoManually(SCORM_ERROR_INVALID_CREDIT,
5231                                  "Invalid credit vocab received from LMS",
5232                                  "strCredit=" + strCredit);
5233        return null;
5234    }
5235}
5236
5237
5238
5239function SCORM_SetObjectiveScore(strObjectiveID, intScore, intMaxScore, intMinScore){
5240
5241    var intObjectiveIndex;
5242    var blnResult;
5243
5244    intScore = RoundToPrecision(intScore, 7);
5245    intMaxScore = RoundToPrecision(intMaxScore, 7);
5246    intMinScore = RoundToPrecision(intMinScore, 7);
5247
5248
5249    WriteToDebug("In SCORM_SetObjectiveScore, strObejctiveID=" + strObjectiveID + ", intScore=" + intScore + ", intMaxScore=" + intMaxScore + ", intMinScore=" + intMinScore);
5250
5251    SCORM_ClearErrorInfo();
5252
5253    intObjectiveIndex = SCORM_FindObjectiveIndexFromID(strObjectiveID);
5254
5255    WriteToDebug("intObjectiveIndex=" + intObjectiveIndex);
5256
5257    blnResult = SCORM_CallLMSSetValue("cmi.objectives." + intObjectiveIndex + ".id", strObjectiveID);
5258    blnResult = SCORM_CallLMSSetValue("cmi.objectives." + intObjectiveIndex + ".score.raw", intScore) && blnResult;
5259    blnResult = SCORM_CallLMSSetValue("cmi.objectives." + intObjectiveIndex + ".score.max", intMaxScore) && blnResult;
5260    blnResult = SCORM_CallLMSSetValue("cmi.objectives." + intObjectiveIndex + ".score.min", intMinScore) && blnResult;
5261
5262    WriteToDebug("Returning " + blnResult);
5263
5264    return blnResult;
5265
5266}
5267
5268function SCORM_SetObjectiveDescription(strObjectiveID, strObjectiveDescription){
5269
5270    var intObjectiveIndex;
5271    var blnResult;
5272
5273    WriteToDebug("In SCORM_SetObjectiveDescription, strObjectiveDescription=" + strObjectiveDescription);
5274    WriteToDebug("Objective Descriptions are not supported prior to SCORM 2004");
5275
5276    SCORM_ClearErrorInfo();
5277
5278    blnResult = SCORM_TRUE;
5279
5280    WriteToDebug("Returning " + blnResult);
5281
5282    return blnResult;
5283
5284}
5285
5286
5287
5288function SCORM_SetObjectiveStatus(strObjectiveID, Lesson_Status){
5289
5290    var intObjectiveIndex;
5291    var blnResult;
5292    var strSCORMStatus = "";
5293
5294    WriteToDebug("In SCORM_SetObjectiveStatus strObjectiveID=" + strObjectiveID + ", Lesson_Status=" + Lesson_Status);
5295
5296    SCORM_ClearErrorInfo();
5297
5298    intObjectiveIndex = SCORM_FindObjectiveIndexFromID(strObjectiveID);
5299
5300    WriteToDebug("intObjectiveIndex=" + intObjectiveIndex);
5301
5302    if (Lesson_Status == LESSON_STATUS_PASSED){
5303        strSCORMStatus = SCORM_PASSED;
5304    }
5305    else if (Lesson_Status == LESSON_STATUS_FAILED){
5306        strSCORMStatus = SCORM_FAILED;
5307    }
5308    else if (Lesson_Status == LESSON_STATUS_COMPLETED){
5309        strSCORMStatus = SCORM_COMPLETED;
5310    }
5311    else if (Lesson_Status == LESSON_STATUS_BROWSED){
5312        strSCORMStatus = SCORM_BROWSED;
5313    }
5314    else if (Lesson_Status == LESSON_STATUS_INCOMPLETE){
5315        strSCORMStatus = SCORM_INCOMPLETE;
5316    }
5317    else if (Lesson_Status == LESSON_STATUS_NOT_ATTEMPTED){
5318        strSCORMStatus = SCORM_NOT_ATTEMPTED;
5319    }
5320
5321    WriteToDebug("strSCORMStatus=" + strSCORMStatus);
5322
5323    blnResult = SCORM_CallLMSSetValue("cmi.objectives." + intObjectiveIndex + ".id", strObjectiveID);
5324    blnResult = SCORM_CallLMSSetValue("cmi.objectives." + intObjectiveIndex + ".status", strSCORMStatus) && blnResult;
5325
5326    WriteToDebug("Returning " + blnResult);
5327
5328    return blnResult;
5329}
5330
5331
5332
5333function SCORM_GetObjectiveScore(strObjectiveID){
5334
5335    var intObjectiveIndex;
5336
5337    WriteToDebug("In SCORM_GetObjectiveScore, strObejctiveID=" + strObjectiveID);
5338
5339    SCORM_ClearErrorInfo();
5340
5341    intObjectiveIndex = SCORM_FindObjectiveIndexFromID(strObjectiveID);
5342
5343    WriteToDebug("intObjectiveIndex=" + intObjectiveIndex);
5344
5345    return SCORM_CallLMSGetValue("cmi.objectives." + intObjectiveIndex + ".score.raw");
5346
5347}
5348
5349
5350function SCORM_GetObjectiveDescription(strObjectiveID){
5351
5352   WriteToDebug("In SCORM_GetObjectiveDescription, strObejctiveID=" + strObjectiveID);
5353   WriteToDebug("ObjectiveDescription is not supported prior to SCORM 2004");
5354   return "";
5355
5356}
5357
5358
5359function SCORM_GetObjectiveStatus(strObjectiveID){
5360
5361    var intObjectiveIndex;
5362    var strStatus;
5363
5364    WriteToDebug("In SCORM_GetObjectiveStatus, strObejctiveID=" + strObjectiveID);
5365
5366    SCORM_ClearErrorInfo();
5367
5368    intObjectiveIndex = SCORM_FindObjectiveIndexFromID(strObjectiveID);
5369
5370    WriteToDebug("intObjectiveIndex=" + intObjectiveIndex);
5371
5372    strStatus = SCORM_CallLMSGetValue("cmi.objectives." + intObjectiveIndex + ".status");
5373
5374    if (strStatus == SCORM_PASSED){
5375        WriteToDebug("returning Passed");
5376        return LESSON_STATUS_PASSED;
5377    }
5378    else if (strStatus == SCORM_FAILED){
5379        WriteToDebug("Returning Failed");
5380        return LESSON_STATUS_FAILED;
5381    }
5382    else if (strStatus == SCORM_COMPLETED){
5383        WriteToDebug("Returning Completed");
5384        return LESSON_STATUS_COMPLETED;
5385    }
5386    else if (strStatus == SCORM_BROWSED){
5387        WriteToDebug("Returning Browsed");
5388        return LESSON_STATUS_BROWSED;
5389    }
5390    else if (strStatus == SCORM_INCOMPLETE){
5391        WriteToDebug("Returning Incomplete");
5392        return LESSON_STATUS_INCOMPLETE;
5393    }
5394    else if (strStatus == SCORM_NOT_ATTEMPTED || strStatus == ""){
5395        WriteToDebug("Returning Not Attempted");
5396        return LESSON_STATUS_NOT_ATTEMPTED;
5397    }
5398    else{
5399        WriteToDebug("ERROR - status not found");
5400        SCORM_SetErrorInfoManually(SCORM_ERROR_INVALID_STATUS,
5401                                "Invalid objective status received from LMS or initial status not yet recorded for objective",
5402                                "strStatus=" + strStatus);
5403        return null;
5404
5405    }
5406
5407}
5408
5409
5410function SCORM_FindObjectiveIndexFromID(strObjectiveID){
5411
5412    var intCount;
5413    var i;
5414    var strTempID;
5415
5416    WriteToDebug("In SCORM_FindObjectiveIndexFromID");
5417
5418    intCount = SCORM_CallLMSGetValue("cmi.objectives._count");
5419
5420    if (intCount == ""){
5421        WriteToDebug("Setting intCount=0");
5422        return 0;
5423    }
5424
5425    intCount = parseInt(intCount, 10);
5426
5427    WriteToDebug("intCount=" + intCount);
5428
5429    for (i=0; i<intCount; i++){
5430
5431        WriteToDebug("Checking index " + i);
5432
5433        strTempID = SCORM_CallLMSGetValue("cmi.objectives." + i + ".id");
5434
5435        WriteToDebug("ID=" + strTempID);
5436
5437        if (strTempID == strObjectiveID){
5438            WriteToDebug("Found Matching index");
5439            return i;
5440        }
5441    }
5442
5443    WriteToDebug("Did not find match, returning count");
5444
5445    return intCount;
5446}
5447
5448function SCORM_CreateValidIdentifier(str) {
5449    return CreateValidIdentifierLegacy(str);
5450}
5451
5452//___________________________________________________________
5453//Interaction Retrieval Functionality
5454//NOTE ON INTERACTION RETRIEVAL
5455//A.  It is only available in certain standards, standards where it is unavailable will return nothing
5456//B.  The interaction records are currently reported using "journaling", whereby each entry is a
5456ppended
5457//      Retrieval methods will retrieve only the most recent value
5458
5459
5460//___________________________________________________________
5461//Helper Methods
5462function SCORM_FindInteractionIndexFromID(strInteractionID){
5463
5464    WriteToDebug("SCORM_FindInteractionIndexFromID - SCORM does not support interaction retrieval, returning null");
5465
5466    return null;
5467}
5468//___________________________________________________________
5469
5470
5471function SCORM_GetInteractionType(strInteractionID)
5472{
5473
5474    WriteToDebug("SCORM_GetInteractionType - SCORM does not support interaction retrieval, returning empty string");
5475    return '';
5476
5477
5478}
5479
5480//public
5481function SCORM_GetInteractionTimestamp(strInteractionID)
5482{
5483    WriteToDebug("SCORM_GetInteractionTimestamp - SCORM does not support interaction retrieval, returning empty string");
5484    return '';
5485}
5486
5487
5488
5489//public
5490function SCORM_GetInteractionCorrectResponses(strInteractionID)
5491{
5492
5493    WriteToDebug("SCORM_GetInteractionCorrectResponses - SCORM does not support interaction retrieval, returning empty array");
5494    return new Array();
5495
5496
5497}
5498
5499
5500
5501//public
5502function SCORM_GetInteractionWeighting(strInteractionID)
5503{
5504    WriteToDebug("SCORM_GetInteractionWeighting - SCORM does not support interaction retrieval, returning empty string");
5505    return '';
5506
5507}
5508
5509
5510
5511//public
5512function SCORM_GetInteractionLearnerResponses(strInteractionID)
5513{
5514    WriteToDebug("SCORM_GetInteractionLearnerResponses - SCORM does not support interaction retrieval, returning empty array");
5515    return new Array();
5516
5517
5518}
5519
5520
5521//public
5522function SCORM_GetInteractionResult(strInteractionID)
5523{
5524    WriteToDebug("SCORM_GetInteractionResult - SCORM does not support interaction retrieval, returning empty string");
5525    return '';
5526
5527}
5528
5529
5530
5531//public
5532function SCORM_GetInteractionLatency(strInteractionID)
5533{
5534    WriteToDebug("SCORM_GetInteractionDescription - SCORM does not support interaction retrieval, returning empty string");
5535    return '';
5536
5537}
5538
5539
5540
5541//public
5542function SCORM_GetInteractionDescription(strInteractionID)
5543{
5544    WriteToDebug("SCORM_GetInteractionDescription - SCORM does not support interaction retrieval, returning empty string");
5545    return '';
5546
5547
5548}
5549
5550//________________________________________________
5551
5552
5553//public
5554function SCORM_CreateDataBucket(strBucketId, intMinSize, intMaxSize){
5555    WriteToDebug("SCORM_CreateDataBucket - SCORM 1.1 and 1.2 do not support SSP, returning false");
5556    return false;
5557}
5558
5559//public
5560function SCORM_GetDataFromBucket(strBucketId){
5561    WriteToDebug("SCORM_GetDataFromBucket - SCORM 1.1 and 1.2 do not support SSP, returning empty string");
5562    return "";
5563}
5564
5565//public
5566function SCORM_PutDataInBucket(strBucketId, strData, blnAppendToEnd){
5567    WriteToDebug("SCORM_PutDataInBucket - SCORM 1.1 and 1.2 do not support SSP, returning false");
5568    return false;
5569}
5570
5571//public
5572function SCORM_DetectSSPSupport(){
5573    WriteToDebug("SCORM_DetectSSPSupport - SCORM 1.1 and 1.2 do not support SSP, returning false");
5574    return false;
5575}
5576
5577//public
5578function SCORM_GetBucketInfo(strBucketId){
5579    WriteToDebug("AICC_DetectSSPSupport - SCORM 1.1 and 1.2 do not support SSP, returning empty SSPBucketSize");
5580    return new SSPBucketSize(0, 0);
5581}
5582
5583//---------------------------------------------------------------------------------
5584//Status Management Functions
5585
5586function SCORM_SetFailed(){
5587    WriteToDebug("In SCORM_SetFailed");
5588    SCORM_ClearErrorInfo();
5589    return SCORM_CallLMSSetValue("cmi.core.lesson_status", SCORM_FAILED);
5590}
5591
5592function SCORM_SetPassed(){
5593    WriteToDebug("In SCORM_SetPassed");
5594    SCORM_ClearErrorInfo();
5595    return SCORM_CallLMSSetValue("cmi.core.lesson_status", SCORM_PASSED);
5596}
5597
5598function SCORM_SetCompleted(){
5599    WriteToDebug("In SCORM_SetCompleted");
5600    SCORM_ClearErrorInfo();
5601    return SCORM_CallLMSSetValue("cmi.core.lesson_status", SCORM_COMPLETED);
5602}
5603
5604function SCORM_ResetStatus(){
5605    WriteToDebug("In SCORM_ResetStatus");
5606    SCORM_ClearErrorInfo();
5607    return SCORM_CallLMSSetValue("cmi.core.lesson_status", SCORM_INCOMPLETE);
5608}
5609
5610function SCORM_GetStatus(){
5611
5612    var strStatus;
5613
5614    WriteToDebug("In SCORM_GetStatus");
5615
5616    SCORM_ClearErrorInfo();
5617
5618    strStatus = SCORM_CallLMSGetValue("cmi.core.lesson_status");
5619
5620    WriteToDebug("strStatus=" + strStatus);
5621
5622    if (strStatus == SCORM_PASSED){
5623        WriteToDebug("returning Passed");
5624        return LESSON_STATUS_PASSED;
5625    }
5626    else if (strStatus == SCORM_FAILED){
5627        WriteToDebug("Returning Failed");
5628        return LESSON_STATUS_FAILED;
5629    }
5630    else if (strStatus == SCORM_COMPLETED){
5631        WriteToDebug("Returning Completed");
5632        return LESSON_STATUS_COMPLETED;
5633    }
5634    else if (strStatus == SCORM_BROWSED){
5635        WriteToDebug("Returning Browsed");
5636        return LESSON_STATUS_BROWSED;
5637    }
5638    else if (strStatus == SCORM_INCOMPLETE){
5639        WriteToDebug("Returning Incomplete");
5640        return LESSON_STATUS_INCOMPLETE;
5641    }
5642    else if (strStatus == SCORM_NOT_ATTEMPTED){
5643        WriteToDebug("Returning Not Attempted");
5644        return LESSON_STATUS_NOT_ATTEMPTED;
5645    }
5646    else{
5647        WriteToDebug("ERROR - status not found");
5648        SCORM_SetErrorInfoManually(SCORM_ERROR_INVALID_STATUS,
5649                                "Invalid lesson status received from LMS",
5650                                "strStatus=" + strStatus);
5651        return null;
5652
5653    }
5654
5655}
5656
5657//public
5658function SCORM_GetProgressMeasure(){
5659    WriteToDebug("SCORM_GetProgressMeasure - SCORM 1.1 and 1.2 do not support progress_measure, returning false");
5660    return false;
5661}
5662//public
5663function SCORM_SetProgressMeasure(){
5664    WriteToDebug("SCORM_SetProgressMeasure - SCORM 1.1 and 1.2 do not support progress_measure, returning false");
5665    return false;
5666}
5667
5668//public
5669function SCORM_GetObjectiveProgressMeasure(){
5670    WriteToDebug("SCORM_GetObjectiveProgressMeasure - SCORM 1.1 and 1.2 do not support progress_measure, returning false");
5671    return false;
5672}
5673//public
5674function SCORM_SetObjectiveProgressMeasure(){
5675    WriteToDebug("SCORM_SetObjectiveProgressMeasure - SCORM 1.1 and 1.2 do not support progress_measure, returning false");
5676    return false;
5677}
5678
5679
5680function SCORM_IsContentInBrowseMode(){
5681
5682    var strLessonMode
5683
5684    WriteToDebug("In SCORM_IsContentInBrowseMode");
5685
5686    strLessonMode = SCORM_CallLMSGetValue("cmi.core.lesson_mode");
5687
5688    WriteToDebug("SCORM_IsContentInBrowseMode,  strLessonMode=" + strLessonMode);
5689
5690    if (strLessonMode == SCORM_BROWSE){
5691        WriteToDebug("Returning true");
5692        return true;
5693    }
5694    else{
5695        WriteToDebug("Returning false");
5696        return false;
5697    }
5698}
5699
5700
5701function SCORM_TranslateExitTypeToSCORM(strExitType){
5702
5703    WriteToDebug("In SCORM_TranslatgeExitTypeToSCORM strExitType-" + strExitType);
5704
5705    if (strExitType == EXIT_TYPE_SUSPEND){
5706        WriteToDebug("Returning suspend");
5707        return SCORM_SUSPEND;
5708    }
5709    else if (strExitType == EXIT_TYPE_UNLOAD){
5710        WriteToDebug("Returning Exit");
5711        return SCORM_NORMAL_EXIT;
5712    }
5713    else if (strExitType == EXIT_TYPE_FINISH){
5714        WriteToDebug("Returning Logout");
5715        return SCORM_NORMAL_EXIT;
5716    }
5717    else if (strExitType == EXIT_TYPE_TIMEOUT){
5718        WriteToDebug("Returning Timout");
5719        return SCORM_TIMEOUT;
5720    }
5721}
5722
5723
5724function SCORM_GetCompletionStatus(){
5725
5726    WriteToDebug("In SCORM_GetCompletionStatus");
5727
5728    if (SCORM_IsContentInBrowseMode()){
5729        WriteToDebug("Returning browsed");
5730        return SCORM_BROWSED;
5731    }
5732    else{
5733        WriteToDebug("Returning Completed");
5734        return SCORM_COMPLETED;
5735    }
5736
5737}
5738
5739
5740function SCORM_SetNavigationRequest(strNavRequest){
5741    WriteToDebug("SCORM_GetNavigationRequest - SCORM 1.1 and 1.2 do not support navigation requests, returning false");
5742    return false;
5743}
5744
5745function SCORM_GetNavigationRequest(){
5746    WriteToDebug("SCORM_GetNavigationRequest - SCORM 1.1 and 1.2 do not support navigation requests, returning false");
5747    return false;
5748}
5749
5750//---------------------------------------------------------------------------------
5751//Functions to Call the SCORM API
5752
5753//note: in all functions that interact with API, we concact any returned strings with "" to convert
5754//the value to a string type, do this b/c many SCORM API's will return a Java String instead of a Javascript
5755//string
5756
5757
5758function SCORM_CallLMSInitialize(){
5759
5760    var strResult;
5761
5762    WriteToDebug("In SCORM_CallLMSInitialize");
5763
5764    SCORM_objAPI = SCORM_GrabAPI();
5765
5766    WriteToDebug("Calling LMSInitialize");
5767
5768    strResult = SCORM_objAPI.LMSInitialize("");
5769    strResult = strResult + "";
5770
5771    WriteToDebug("strResult=" + strResult);
5772
5773    if (strResult == SCORM_FALSE){
5774
5775        WriteToDebug("Detected failed call to initialize");
5776
5777        SCORM_SetErrorInfo();
5778
5779        WriteToDebug ("Error calling LMSInitialize:");
5780        WriteToDebug ("              intSCORMError=" + intSCORMError);
5781        WriteToDebug ("              SCORMErrorString=" + strSCORMErrorString);
5782        WriteToDebug ("              Diagnostic=" + strSCORMErrorDiagnostic);
5783
5784        return false;
5785
5786    }
5787
5788    WriteToDebug("Returning true");
5789
5790    return true;
5791}
5792
5793function SCORM_CallLMSSetValue(strElement, strValue){
5794
5795    var strResult;
5796
5797    WriteToDebug("SCORM_CallLMSSetValue strElement=" + strElement + ", strValue=" + strValue);
5798
5799    if (blnReviewModeSoReadOnly === true){
5800        WriteToDebug("Mode is Review and configuration setting dictates this should be read only so exiting.");
5801        return true;
5802    }
5803
5804    SCORM_objAPI = SCORM_GrabAPI();
5805
5806    WriteToDebug("Calling LMSSetValue");
5807
5808    strElement = strElement + "";
5809    strValue = strValue + "";
5810
5811    strResult = SCORM_objAPI.LMSSetValue(strElement, strValue)
5812    strResult = strResult + "";
5813
5814    WriteToDebug("strResult=" + strResult);
5815
5816    if (strResult == SCORM_FALSE){
5817
5818        WriteToDebug("Detected Failed call to LMSSetvalue");
5819
5820        SCORM_SetErrorInfo();
5821
5822        WriteToDebug ("Error calling LMSSetValue:");
5823        WriteToDebug ("              strElement=" + strElement);
5824        WriteToDebug ("              strValue=" + strValue);
5825        WriteToDebug ("              intSCORMError=" + intSCORMError);
5826        WriteToDebug ("              SCORMErrorString=" + strSCORMErrorString);
5827        WriteToDebug ("              Diagnostic=" + strSCORMErrorDiagnostic);
5828
5829        return false;
5830    }
5831
5832    WriteToDebug("Returning true");
5833
5834    return true;
5835}
5836
5837function SCORM_CallLMSGetValue(strElement){
5838
5839    var strResult;
5840
5841    WriteToDebug("In SCORM_CallLMSGetValue strElement=" + strElement);
5842
5843    SCORM_objAPI = SCORM_GrabAPI();
5844
5845    WriteToDebug("Call LMSGetValue");
5846
5847    strElement = strElement + "";
5848
5849    strResult = SCORM_objAPI.LMSGetValue(strElement) + ""
5850
5851    WriteToDebug("strResult=" + strResult);
5852
5853    intSCORMError = SCORM_objAPI.LMSGetLastError()
5854    intSCORMError = intSCORMError + "";
5855
5856    WriteToDebug("intSCORMError=" + intSCORMError);
5857
5858    if (intSCORMError != SCORM_NO_ERROR){
5859
5860        WriteToDebug("Detected failed called to LMSGetValue");
5861
5862        SCORM_SetErrorInfo();
5863
5864        WriteToDebug ("Error calling LMSGetValue:");
5865        WriteToDebug ("              strElement=" + strElement);
5866        WriteToDebug ("              intSCORMError=" + intSCORMError);
5867        WriteToDebug ("              SCORMErrorString=" + strSCORMErrorString);
5868        WriteToDebug ("              Diagnostic=" + strSCORMErrorDiagnostic);
5869    }
5870
5871    WriteToDebug("Returning " + strResult);
5872
5873    return strResult;
5874
5875}
5876
5877
5878function SCORM_CallLMSCommit(){
5879
5880    var strResult;
5881
5882    WriteToDebug("In SCORM_CallLMSCommit");
5883
5884    SCORM_objAPI = SCORM_GrabAPI();
5885
5886    WriteToDebug("Calling LMSCommit");
5887
5888    strResult = SCORM_objAPI.LMSCommit("");
5889
5890    strResult = strResult + "";
5891
5892    WriteToDebug("strResult=" + strResult);
5893
5894    if (strResult == SCORM_FALSE){
5895
5896        WriteToDebug("Detected failed call to LMSCommit");
5897
5898        SCORM_SetErrorInfo();
5899
5900        WriteToDebug ("Error calling LMSCommit:");
5901        WriteToDebug ("              intSCORMError=" + intSCORMError);
5902        WriteToDebug ("              SCORMErrorString=" + strSCORMErrorString);
5903        WriteToDebug ("              Diagnostic=" + strSCORMErrorDiagnostic);
5904
5905        return false;
5906    }
5907
5908    WriteToDebug("Returning true");
5909
5910    return true;
5911}
5912
5913
5914function SCORM_CallLMSFinish(){
5915
5916    var strResult;
5917
5918    WriteToDebug("In SCORM_CallLMSFinish");
5919
5920    SCORM_objAPI = SCORM_GrabAPI();
5921
5922    WriteToDebug("Calling LMS Finish");
5923
5924    strResult = SCORM_objAPI.LMSFinish("");
5925
5926    strResult = strResult + "";
5927
5928    WriteToDebug("strResult=" + strResult);
5929
5930    if (strResult == SCORM_FALSE){
5931
5932        WriteToDebug("Detected failed call to LMSFinish");
5933
5934        SCORM_SetErrorInfo();
5935
5936        WriteToDebug ("Error calling LMSFinish:");
5937        WriteToDebug ("              intSCORMError=" + intSCORMError);
5938        WriteToDebug ("              SCORMErrorString=" + strSCORMErrorString);
5939        WriteToDebug ("              Diagnostic=" + strSCORMErrorDiagnostic);
5940
5941        return false;
5942    }
5943
5944    WriteToDebug("Returning True");
5945
5946    return true;
5947}
5948
5949
5950//---------------------------------------------------------------------------------
5951//Error Handling Functions
5952function SCORM_ClearErrorInfo(){
5953
5954    WriteToDebug("In SCORM_ClearErrorInfo");
5955
5956    intSCORMError = SCORM_NO_ERROR;
5957    strSCORMErrorString = "";
5958    strSCORMErrorDiagnostic = "";
5959}
5960
5961function SCORM_SetErrorInfo(){
5962
5963    WriteToDebug("In SCORM_SetErrorInfo");
5964
5965    intSCORMError = SCORM_objAPI.LMSGetLastError();
5966    strSCORMErrorString = SCORM_objAPI.LMSGetErrorString(intSCORMError);
5967    strSCORMErrorDiagnostic = SCORM_objAPI.LMSGetDiagnostic("");
5968
5969    intSCORMError = intSCORMError + "";
5970    strSCORMErrorString = strSCORMErrorString + "";
5971    strSCORMErrorDiagnostic = strSCORMErrorDiagnostic + "";
5972
5973    WriteToDebug("intSCORMError=" + intSCORMError);
5974    WriteToDebug("strSCORMErrorString=" + strSCORMErrorString);
5975    WriteToDebug("strSCORMErrorDiagnostic=" + strSCORMErrorDiagnostic);
5976}
5977
5978function SCORM_SetErrorInfoManually(intNum, strString, strDiagnostic){
5979
5980        WriteToDebug("In SCORM_SetErrorInfoManually");
5981        WriteToDebug("ERROR-Num=" + intNum);
5982        WriteToDebug("      String=" + strString);
5983        WriteToDebug("      Diag=" + strDiagnostic);
5984
5985        intSCORMError = intNum;
5986        strSCORMErrorString = strString;
5987        strSCORMErrorDiagnostic = strDiagnostic;
5988
5989}
5990
5991function SCORM_GetLastError(){
5992
5993    WriteToDebug("In SCORM_GetLastError");
5994
5995    if (intSCORMError == SCORM_NO_ERROR){
5996        WriteToDebug("Returning No Error");
5997        return NO_ERROR;
5998    }
5999    else {
6000        WriteToDebug("Returning " + intSCORMError);
6001        return intSCORMError;
6002    }
6003}
6004
6005function SCORM_GetLastErrorDesc(){
6006    WriteToDebug("In SCORM_GetLastErrorDesc, " + strSCORMErrorString + "\n" + strSCORMErrorDiagnostic);
6007    return strSCORMErrorString + "\n" + strSCORMErrorDiagnostic;
6008}
6009
6010
6011
6012//---------------------------------------------------------------------------------
6013//API Locating Functions
6014
6015function SCORM_GrabAPI(){
6016
6017    WriteToDebug("In SCORM_GrabAPI");
6018
6019    //if we haven't already located the API, find it using our improved ADL algorithm
6020    if (typeof(SCORM_objAPI) == "undefined" || SCORM_objAPI == null){
6021        WriteToDebug("Searching with improved ADL algorithm");
6022        SCORM_objAPI = SCORM_GetAPI();
6023    }
6024
6025    //if it's still not found, look in every concievable spot...some older LMS's bury it in 
6025wierd places
6026    //drop this because it can cause problems when the content is launched in a cross domain envrionment...for instance the
6027    //standard detection algorithm could come upon a frame from a different domain using this algorithm when the content is
6028    //launched under AICC
6029
6030    //TODO: a better solution might be to wrap this in a try/catch block
6031
6032    //if (typeof(SCORM_objAPI) == "undefined" || SCORM_objAPI == null){
6033    //  WriteToDebug("Searching everywhere with Rustici Software algorithm");
6034    //  SCORM_objAPI = SCORM_SearchForAPI(window);
6035    //}
6036
6037    if (typeof(SCORM_objAPI) == "undefined" || SCORM_objAPI == null){
6038        SCORM_objAPI = SCORM_SearchForAPI(window);
6039    }
6040
6041    WriteToDebug("SCORM_GrabAPI, returning");
6042
6043    return SCORM_objAPI;
6044
6045}
6046
6047
6048function SCORM_SearchForAPI(wndLookIn){
6049
6050    WriteToDebug("SCORM_SearchForAPI");
6051
6052    var objAPITemp = null;
6053    var strDebugID = "";
6054
6055    strDebugID = "Name=" + wndLookIn.name + ", href=" + wndLookIn.location.href
6056
6057    objAPITemp = wndLookIn.API;
6058
6059    if (SCORM_APIFound(objAPITemp)){
6060        WriteToDebug("Found API in this window - "  + strDebugID);
6061        return objAPITemp;
6062    }
6063
6064    if (SCORM_WindowHasParent(wndLookIn)){
6065        WriteToDebug("Searching Parent - "  + strDebugID);
6066        objAPITemp = SCORM_SearchForAPI(wndLookIn.parent);
6067    }
6068
6069    if (SCORM_APIFound(objAPITemp)){
6070        WriteToDebug("Found API in a parent - "  + strDebugID);
6071        return objAPITemp;
6072    }
6073
6074    if (SCORM_WindowHasOpener(wndLookIn)){
6075        WriteToDebug("Searching Opener - "  + strDebugID);
6076        objAPITemp = SCORM_SearchForAPI(wndLookIn.opener);
6077    }
6078
6079    if (SCORM_APIFound(objAPITemp)){
6080        WriteToDebug("Found API in an opener - "  + strDebugID);
6081        return objAPITemp;
6082    }
6083
6084    //look in child frames individually, don't call this function recursively
6085    //on them to prevent an infinite loop when it looks back up to the parents
6086    WriteToDebug("Looking in children - "  + strDebugID);
6087    objAPITemp = SCORM_LookInChildren(wndLookIn);
6088
6089    if (SCORM_APIFound(objAPITemp)){
6090        WriteToDebug("Found API in Children - "  + strDebugID);
6091        return objAPITemp;
6092    }
6093
6094    WriteToDebug("Didn't find API in this window - "  + strDebugID);
6095    return null;
6096}
6097
6098
6099function SCORM_LookInChildren(wnd){
6100
6101    WriteToDebug("SCORM_LookInChildren");
6102
6103    var objAPITemp = null;
6104
6105    var strDebugID = "";
6106
6107    strDebugID = "Name=" + wnd.name + ", href=" + wnd.location.href
6108
6109    for (var i=0; i < wnd.frames.length; i++){
6110
6111        WriteToDebug("Looking in child frame " + i);
6112
6113        objAPITemp = wnd.frames[i].API;
6114
6115        if (SCORM_APIFound(objAPITemp)){
6116            WriteToDebug("Found API in child frame of " + strDebugID);
6117            return objAPITemp;
6118        }
6119
6120        WriteToDebug("Looking in this child's children " + strDebugID);
6121        objAPITemp = SCORM_LookInChildren(wnd.frames[i]);
6122
6123        if (SCORM_APIFound(objAPITemp)){
6124            WriteToDebug("API found in this child's children " + strDebugID);
6125            return objAPITemp;
6126        }
6127    }
6128
6129    return null;
6130}
6131
6132function SCORM_WindowHasOpener(wnd){
6133    WriteToDebug("In SCORM_WindowHasOpener");
6134    if ((wnd.opener != null) && (wnd.opener != wnd) && (typeof(wnd.opener) != "undefined")){
6135        WriteToDebug("Window Does Have Opener");
6136        return true;
6137    }
6138    else{
6139        WriteToDebug("Window Does Not Have Opener");
6140        return false;
6141    }
6142}
6143
6144function SCORM_WindowHasParent(wnd){
6145    WriteToDebug("In SCORM_WindowHasParent");
6146    if ((wnd.parent != null) && (wnd.parent != wnd) && (typeof(wnd.parent) != "undefined")){
6147        WriteToDebug("Window Does Have Parent");
6148        return true;
6149    }
6150    else{
6151        WriteToDebug("Window Does Not Have Parent");
6152        return false;
6153    }
6154}
6155
6156
6157function SCORM_APIFound(obj){
6158    WriteToDebug("In SCORM_APIFound");
6159    if (obj == null || typeof(obj) == "undefined"){
6160        WriteToDebug("API NOT Found");
6161        return false;
6162    }
6163    else{
6164        WriteToDebug("API Found");
6165        return true;
6166    }
6167}
6168
6169
6170
6171
6172/*******************************************************************
6173    * SCORM 2004 API Search Algorithm
6174    * Description - Improvement of the algorithm developed by ADL to
6175        find the SCORM 2004 API Adapter. The improvements eliminate
6176        errors, improve code clarity and eliminate the dependence
6177        on global variables. The errors removed include:
6178            - The "win" variable was never declared in a scope
6179              accessible to the GetAPI function
6180            - A call to API.version which is not part of the SCORM
6181              2004 specification
6182            - The previous algorithm was not able to find the API
6183              if it was located in the window's parent's opener
6184    * Original Author - ADL & Concurrent Technologies Corporation
6185    * Author -  Mike Rustici (April 1, 2004)
6186                Rustici Software, LLC
6187                http://www.scorm.com
6188                [email protected]
6189*******************************************************************/
6190
6191
6192
6193
6194/*
6195ScanParentsForApi
6196-Searches all the parents of a given window until
6197 it finds an object named "API". If an
6198 object of that name is found, a reference to it
6199 is returned. Otherwise, this function returns null.
6200*/
6201function SCORM_ScanParentsForApi(win)
6202{
6203
6204    WriteToDebug("In SCORM_ScanParentsForApi, win=" + win.location);
6205
6206    /*
6207    Establish an outrageously high maximum number of
6208    parent windows that we are will to search as a
6209    safe guard against an infinite loop. This is
6210    probably not strictly necessary, but different
6211    browsers can do funny things with undefined objects.
6212    */
6213    var MAX_PARENTS_TO_SEARCH = 500;
6214    var nParentsSearched = 0;
6215
6216    /*
6217    Search each parent window until we either:
6218         -find the API,
6219         -encounter a window with no parent (parent is null
6220                or the same as the current window)
6221         -or, have reached our maximum nesting threshold
6222    */
6223    while ( (win.API == null || win.API === undefined) &&
6224            (win.parent != null) && (win.parent != win) &&
6225            (nParentsSearched <= MAX_PARENTS_TO_SEARCH)
6226          )
6227    {
6228
6229        nParentsSearched++;
6230        win = win.parent;
6231    }
6232
6233    /*
6234    If the API doesn't exist in the window we stopped looping on,
6235    then this will return null.
6236    */
6237    return win.API;
6238}
6239
6240
6241/*
6242GetAPI
6243-Searches all parent and opener windows relative to the
6244 current window for the SCORM API Adapter.
6245 Returns a reference to the API Adapter if found or null
6246 otherwise.
6247*/
6248function SCORM_GetAPI()
6249{
6250    WriteToDebug("In SCORM_GetAPI");
6251
6252    var API = null;
6253
6254    //Search all the parents of the current window if there are any
6255    if ((window.parent != null) && (window.parent != window))
6256    {
6257        WriteToDebug("SCORM_GetAPI, searching parent");
6258        API = SCORM_ScanParentsForApi(window.parent);
6259    }
6260
6261    /*
6262    If we didn't find the API in this window's chain of parents,
6263    then search all the parents of the opener window if there is one
6264    */
6265    if ((API == null) && (window.top.opener != null))
6266    {
6267        WriteToDebug("SCORM_GetAPI, searching opener");
6268        API = SCORM_ScanParentsForApi(window.top.opener);
6269    }
6270
6271    return API;
6272}
6273
6274
6275var STANDARD = 'AICC';
6276
6277var blnDirtyAICCData = false;
6278var blnCommitSavedData = false;
6279
6280var intAICCErrorNum = NO_ERROR;
6281var strAICCErrorDesc = "";
6282
6283var aryAICCFoundItems = new Array();    //stores a list of data elements that have been found in a AICC GetParam result
6284
6285var blnUseLongInteractionResultValues = true;   //we start off trying to submit verbose descriptions of interaction results, if this errors, we revert back to strict conformance
6286
6287var blnReviewModeSoReadOnly = false;
6288
6289//these variables come from and can go stright to the LMS
6290var AICC_LMS_Version = "";
6291var AICC_Student_ID = "";
6292var AICC_Student_Name = "";
6293var AICC_Lesson_Location = "";
6294var AICC_Score = "";
6295var AICC_Credit = "";
6296var AICC_Lesson_Status = "";
6297var AICC_Time = "";
6298var AICC_Mastery_Score = "";
6299var AICC_Lesson_Mode = "";
6300var AICC_Max_Time_Allowed = "";
6301var AICC_Time_Limit_Action = "";
6302var AICC_Audio = "";
6303var AICC_Speed = "";
6304var AICC_Language = "";
6305var AICC_Text = "";
6306var AICC_Launch_Data = "";
6307var AICC_Data_Chunk = "";
6308var AICC_Comments = "";
6309var AICC_Objectives = null;
6310var AICC_CourseID = "";
6311
6312//these variables are more logical representations of the above variables
6313var AICC_fltScoreRaw = "";
6314var AICC_fltScoreMax = "";
6315var AICC_fltScoreMin = "";
6316var AICC_blnCredit = true;
6317var AICC_strLessonMode = MODE_NORMAL;
6318var AICC_intPreviouslyAccumulatedMilliseconds = 0;
6319var AICC_intMaxTimeAllowedMilliseconds = MAX_CMI_TIME;
6320var AICC_blnExitOnTimeout = false;
6321var AICC_blnShowMessageOnTimeout = true;
6322var AICC_TextPreference = PREFERENCE_DEFAULT;
6323var AICC_Status = LESSON_STATUS_NOT_ATTEMPTED;
6324var AICC_Entry = AICC_ENTRY_FLAG_DEFAULT;               //must be the default b/c absense of entry flag indicates "review"
6325var AICC_AudioPlayPreference = PREFERENCE_DEFAULT;
6326var AICC_intAudioVolume = 100;
6327var AICC_intPercentOfMaxSpeed = 100;
6328var AICC_intSessionTimeMilliseconds = 0;
6329var AICC_aryObjectivesRead = new Array();   //objectives reported from LMS, read results from here, also store copy of currently set objectives here
6330var AICC_aryObjectivesWrite = new Array();  //objectives altered during this session (that is all we're supposed to report to AICC LMS)
6331var AICC_aryCommentsFromLearner = new Array();
6332var AICC_aryInteractions = new Array();
6333
6334//constants to define the meaning of the positions in the arrays to store objective information
6335//these arrays contain a top level array index by the obj index, these positions contain subarrays with these indicies
6336var AICC_OBJ_ARRAY_ID     = 0;
6337var AICC_OBJ_ARRAY_SCORE  = 1;
6338var AICC_OBJ_ARRAY_STATUS = 2;
6339
6340//constants to define the meaning of the positions in the arrays to store interaction information
6341var AICC_INTERACTIONS_ID                    = 0;
6342var AICC_INTERACTIONS_RESPONSE              = 1;
6343var AICC_INTERACTIONS_CORRECT               = 2;
6344var AICC_INTERACTIONS_CORRECT_RESPONSE      = 3;
6345var AICC_INTERACTIONS_TIME_STAMP            = 4;
6346var AICC_INTERACTIONS_TYPE                  = 5;
6347var AICC_INTERACTIONS_WEIGHTING             = 6;
6348var AICC_INTERACTIONS_LATENCY               = 7;
6349var AICC_INTERACTIONS_RESPONSE_LONG         = 8;
6350var AICC_INTERACTIONS_CORRECT_RESPONSE_LONG = 9;
6351
6352var AICC_INTERACTION_TYPE_TRUE_FALSE    = "T";
6353var AICC_INTERACTION_TYPE_CHOICE        = "C";
6354var AICC_INTERACTION_TYPE_FILL_IN       = "F";
6355var AICC_INTERACTION_TYPE_MATCHING      = "M";
6356var AICC_INTERACTION_TYPE_PERFORMANCE   = "P";
6357var AICC_INTERACTION_TYPE_SEQUENCING    = "S";
6358var AICC_INTERACTION_TYPE_LIKERT        = "L";
6359var AICC_INTERACTION_TYPE_NUMERIC       = "N";
6360
6361var AICC_RESULT_CORRECT         = "C";
6362var AICC_RESULT_WRONG           = "W";
6363var AICC_RESULT_UNANTICIPATED   = "U";
6364var AICC_RESULT_NEUTRAL         = "N";
6365
6366
6367//error constants
6368var AICC_NO_ERROR = "0";
6369var AICC_ERROR_INVALID_PREFERENCE = "-1";
6370var AICC_ERROR_INVALID_STATUS = "-2";
6371var AICC_ERROR_INVALID_SPEED = "-3";
6372var AICC_ERROR_INVALID_TIMESPAN = "-4";
6373var AICC_ERROR_INVALID_TIME_LIMIT_ACTION = "-5";
6374var AICC_ERROR_INVALID_DECIMAL = "-6";
6375var AICC_ERROR_INVALID_CREDIT = "-7";
6376var AICC_ERROR_INVALID_LESSON_MODE = "-8";
6377var AICC_ERROR_INVALID_ENTRY = "-9";
6378
6379var blnReviewModeSoReadOnly = false;
6380
6381function AICC_Initialize(){
6382
6383    WriteToDebug("In AICC_Initialize");
6384
6385    //make getparam request, this will parse the data into local variables then begin loading the content
6386
6387    window.AICCComm.MakeGetParamRequest();
6388
6389    return;
6390}
6391
6392function AICC_InitializeExecuted(){
6393    WriteToDebug("In AICC_InitializeExecuted");
6394    //only reset status and such if we are not reviewing
6395    if (AICC_GetLessonMode() != MODE_REVIEW){
6396        //only set the status to incomplete if it's not attempted yet
6397        if (AICC_GetStatus() == LESSON_STATUS_NOT_ATTEMPTED){
6398            WriteToDebug("Setting Status to Incomplete");
6399            AICC_Status = LESSON_STATUS_INCOMPLETE;
6400        }
6401    }
6402    else{
6403        //mode is review, check if we should go to read only mode
6404        if (!(typeof(REVIEW_MODE_IS_READ_ONLY) == "undefined") && REVIEW_MODE_IS_READ_ONLY === true){
6405            blnReviewModeSoReadOnly = true;
6406        }
6407    }
6408}
6409
6410
6411function AICC_Finish(strExitType, blnStatusWasSet){
6412
6413    WriteToDebug("In AICC_Finish, strExitType=" + strExitType + ", blnStatusWasSet=" + blnStatusWasSet);
6414
6415    if (! blnStatusWasSet){
6416        if ( (strExitType == EXIT_TYPE_FINISH)){
6417
6418            WriteToDebug("Setting status to complete");
6419
6420            AICC_Status = LESSON_STATUS_COMPLETED;
6421        }
6422        else{
6423            WriteToDebug("Setting status to incomplete");
6424
6425            AICC_Status = LESSON_STATUS_INCOMPLETE;
6426        }
6427    }
6428
6429    window.AICCComm.PrepareCourseExit(this.actionConceded);
6430
6431    //Commit Data
6432    AICC_CommitData();
6433
6434    //Make ExitAU Request
6435    window.AICCComm.MakeExitAURequest();
6436
6437    return true;
6438}
6439
6440
6441function AICC_CommitData(){
6442
6443    var strAICCData;
6444
6445    WriteToDebug("In AICC_CommitData");
6446
6447    if (blnReviewModeSoReadOnly === true){
6448        WriteToDebug("Mode is Review and configuration setting dictates this should be read only so exiting.");
6449        return true;
6450    }
6451
6452    //if there is data to save, make a putparam request
6453    blnCommitSavedData = false;
6454    if (IsThereDirtyAICCData()){
6455        AICC_SaveTime(GetSessionAccumulatedTime());
6456
6457        blnCommitSavedData = true;
6458
6459        WriteToDebug("Found Dirty Data");
6460
6461        strAICCData = FormAICCPostData();
6462
6463        window.AICCComm.MakePutParamRequest(strAICCData);
6464
6465        //if there is no course id, then the LMS doesn't support interactions
6466        //MR - 5/24/05 - removed the course id check to allow aicc courses to be imported into an LMS without using the descriptor files
6467        //also, Saba doesn't send the Course Id field even when imported with the descriptor files
6468        if ( /*AICC_CourseID != "" && */ AICC_aryInteractions.length > 0){
6469
6470            WriteToDebug("Saving Interactions");
6471
6472            AICC_SendInteractions();
6473        }
6474
6475        ClearDirtyAICCData();   //if the PutParam request fails, the data will be set back to dirty via a call to AICC_PutParamFailed()
6476
6477    }
6478
6479    return true;
6480}
6481
6482
6483function AICC_SendInteractions(){
6484
6485    WriteToDebug("In AICC_SendInteractions.");
6486
6487    if (blnReviewModeSoReadOnly === true){
6488        WriteToDebug("Mode is Review and configuration setting dictates this should be read only so exiting.");
6489        return true;
6490    }
6491
6492    var strAICCData = FormAICCInteractionsData();
6493    window.AICCComm.MakePutInteractionsRequest(strAICCData);
6494    //AICC interactions are additive, so we need to remove them once they are sent
6495    //now that we've sent this batch of interactions, clear out the AICC_aryInteractions array for new ones
6496    AICC_aryInteractions = new Array();
6497}
6498
6499function AICC_GetStudentID(){
6500    WriteToDebug("In AICC_GetStudentID, Returning " + AICC_Student_ID);
6501
6502    return AICC_Student_ID ;
6503}
6504
6505function AICC_GetStudentName(){
6506    WriteToDebug("In AICC_GetStudentName, Returning " + AICC_Student_Name);
6507
6508    return AICC_Student_Name;
6509}
6510
6511function AICC_GetBookmark(){
6512    WriteToDebug("In AICC_GetBookmark, Returning " + AICC_Lesson_Location);
6513
6514    return AICC_Lesson_Location;
6515}
6516
6517function AICC_SetBookmark(strBookmark){
6518    WriteToDebug("In AICC_SetBookmark, strBookmark=" + strBookmark);
6519
6520    SetDirtyAICCData();
6521
6522    AICC_Lesson_Location = strBookmark;
6523
6524    return true;
6525}
6526
6527function AICC_GetDataChunk(){
6528    WriteToDebug("In AICC_GetDataChunk, Returning " + AICC_Data_Chunk);
6529    return AICC_Data_Chunk ;
6530}
6531
6532function AICC_SetDataChunk(strData){
6533    //need to check for character limits here 4096 characters
6534    WriteToDebug("In AICC_SetDataChunk, strData=" + strData );
6535    if(USE_STRICT_SUSPEND_DATA_LIMITS==true) {
6536        if(strData.length > 4096) {
6537            WriteToDebug("SCORM_SetDataChunk - suspend_data too large (4096 character limit for AICC)");
6538            return false;
6539        }else{
6540            SetDirtyAICCData();
6541            AICC_Data_Chunk = strData;
6542            AICC_CommitData();
6543            return true;
6544        }
6545    }else{
6546        SetDirtyAICCData();
6547        AICC_Data_Chunk = strData;
6548        AICC_CommitData();
6549        return true;
6550    }
6551}
6552
6553
6554function AICC_GetLaunchData(){
6555    WriteToDebug("In AICC_GetLaunchData, Returning " + AICC_Launch_Data );
6556
6557    return AICC_Launch_Data ;
6558}
6559
6560function AICC_GetComments(){
6561    WriteToDebug("In AICC_GetComments, Returning " + AICC_aryCommentsFromLearner.join(" | "));
6562
6563    //not available in AICC - return cached comments from this session
6564    return AICC_aryCommentsFromLearner.join(" | ");
6565}
6566
6567function AICC_WriteComment(strComment){
6568    WriteToDebug("In AICC_WriteComment, strComment=" + strComment);
6569
6570    var intNextIndex;
6571
6572    //remove the "|" since AICC has its own delimiters
6573    if (strComment.search(/ \| /) == 0){
6574        strComment = strComment.substr(3);
6575    }
6576
6577    //remove encoding of "|"
6578    strComment.replace(/\|\|/g, "|")
6579
6580    //add the comment to an array of comments
6581    intNextIndex = AICC_aryCommentsFromLearner.length;
6582
6583    WriteToDebug("Adding comment to array");
6584
6585    AICC_aryCommentsFromLearner[intNextIndex] = strComment;
6586
6587    SetDirtyAICCData();
6588
6589    return true;
6590}
6591
6592function AICC_GetLMSComments(){
6593    WriteToDebug("In AICC_GetLMSComments, Returning " + AICC_Comments );
6594
6595    return AICC_Comments;
6596}
6597
6598
6599function AICC_GetAudioPlayPreference(){
6600    WriteToDebug("In AICC_GetAudioPlayPreference, Returning " + AICC_AudioPlayPreference);
6601
6602    return AICC_AudioPlayPreference;
6603}
6604
6605
6606
6607function AICC_GetAudioVolumePreference(){
6608    WriteToDebug("In AICC_GetAudioVolumePreference, Returning " + AICC_intAudioVolume);
6609
6610    return AICC_intAudioVolume;
6611}
6612
6613
6614function AICC_SetAudioPreference(PlayPreference, intPercentOfMaxVolume){
6615    WriteToDebug("In AICC_SetAudioPreference, Returning true");
6616
6617    AICC_AudioPlayPreference = PlayPreference;
6618    AICC_intAudioVolume = intPercentOfMaxVolume;
6619
6620    SetDirtyAICCData();
6621    return true;
6622}
6623
6624
6625
6626
6627function AICC_SetLanguagePreference(strLanguage){
6628    WriteToDebug("In AICC_SetLanguagePreference, Returning true");
6629
6630    SetDirtyAICCData();
6631
6632    AICC_Language = strLanguage;
6633
6634    return true;
6635}
6636
6637
6638function AICC_GetLanguagePreference(){
6639    WriteToDebug("In AICC_GetLanguagePreference, Returning " + AICC_Language);
6640
6641    return AICC_Language;
6642}
6643
6644
6645function AICC_SetSpeedPreference(intPercentOfMax){
6646    WriteToDebug("In AICC_SetSpeedPreference, Returning true");
6647
6648    AICC_intPercentOfMaxSpeed = intPercentOfMax;
6649
6650    SetDirtyAICCData();
6651
6652    return true;
6653}
6654
6655
6656function AICC_GetSpeedPreference(){
6657    WriteToDebug("In AICC_GetSpeedPreference, Returning " + AICC_intPercentOfMaxSpeed);
6658
6659    return AICC_intPercentOfMaxSpeed;
6660}
6661
6662function AICC_SetTextPreference(intPreference){
6663    WriteToDebug("In AICC_SetTextPreference, Returning true");
6664
6665    AICC_TextPreference = intPreference;
6666
6667    SetDirtyAICCData();
6668
6669    return true;
6670}
6671
6672
6673function AICC_GetTextPreference(){
6674    WriteToDebug("In AICC_GetTextPreference, Returning " + AICC_TextPreference);
6675
6676    return AICC_TextPreference;
6677}
6678
6679function AICC_GetPreviouslyAccumulatedTime(){
6680    WriteToDebug("In AICC_GetPreviouslyAccumulatedTime, Returning " + AICC_intPreviouslyAccumulatedMilliseconds);
6681
6682    return AICC_intPreviouslyAccumulatedMilliseconds;
6683}
6684
6685
6686function AICC_SaveTime(intMilliSeconds){
6687    WriteToDebug("In intMilliSeconds, Returning true");
6688
6689    AICC_intSessionTimeMilliseconds = intMilliSeconds;
6690
6691    return true;
6692}
6693
6694function AICC_GetMaxTimeAllowed(){
6695    WriteToDebug("In AICC_GetMaxTimeAllowed, Returning " + AICC_intMaxTimeAllowedMilliseconds);
6696
6697    return AICC_intMaxTimeAllowedMilliseconds;
6698}
6699
6700
6701
6702function AICC_DisplayMessageOnTimeout(){
6703    WriteToDebug("In AICC_DisplayMessageOnTimeout, Returning " + AICC_blnShowMessageOnTimeout);
6704
6705    return AICC_blnShowMessageOnTimeout;
6706}
6707
6708function AICC_ExitOnTimeout(){
6709    WriteToDebug("In AICC_ExitOnTimeout, Returning " + AICC_blnExitOnTimeout);
6710
6711    return AICC_blnExitOnTimeout;
6712}
6713
6714
6715function AICC_GetPassingScore(){
6716    WriteToDebug("In AICC_GetPassingScore, Returning " + AICC_Mastery_Score);
6717
6718    return AICC_Mastery_Score;
6719
6720}
6721
6722
6723
6724function AICC_GetScore(){
6725    WriteToDebug("In AICC_GetScore, Returning " + AICC_fltScoreRaw);
6726
6727    return AICC_fltScoreRaw;
6728}
6729
6730function AICC_SetScore(fltScore, fltMaxScore, fltMinScore){
6731    WriteToDebug("In AICC_SetScore, fltScore=" + fltScore + ", fltMaxScore=" + fltMaxScore + ", fltMinScore=" + fltMinScore);
6732
6733    AICC_fltScoreRaw = fltScore;
6734    AICC_fltScoreMax = fltMaxScore;
6735    AICC_fltScoreMin = fltMinScore;
6736
6737    SetDirtyAICCData();
6738
6739    return true;
6740}
6741
6742
6743function AICC_RecordTrueFalseInteraction(strID, blnResponse, blnCorrect, blnCorrectResponse, strDescription, intWeighting, intLatency, strLearningObjectiveID, dtmTime){
6744
6745    WriteToDebug("In AICC_RecordTrueFalseInteraction strID=" + strID + ", blnResponse=" + blnResponse +
6746                    ", blnCorrect=" + blnCorrect + ", blnCorrectResponse=" + blnCorrectResponse +
6747                    ", strDescription=" + strDescription + ", intWeighting=" + intWeighting + ", intLatency=" + intLatency +
6748                    ", strLearningObjectiveID=" + strLearningObjectiveID + ", dtmTime=" + dtmTime);
6749
6750
6751    var intTotalInteractions;
6752    var aryData = new Array(10);
6753
6754    intTotalInteractions = AICC_aryInteractions.length;
6755
6756    if (intWeighting == null || intWeighting == undefined ){intWeighting="";}
6757    if (intLatency == null || intLatency == undefined){intLatency="";}
6758    if (blnCorrect == null || blnCorrect == undefined){blnCorrect="";}
6759
6760    var strResponse = "";
6761    var strCorrectResponse = "";
6762
6763    if (blnResponse !== null) {
6764        if (blnResponse) {
6765            strResponse = "t";
6766        }
6767        else {
6768            strResponse = "f";
6769        }
6770    }
6771
6772    if (blnCorrectResponse == true){
6773        strCorrectResponse = "t";
6774    }
6775    else if(blnCorrectResponse == false){       //test for false b/c it could be null in which case we want to leave it as ""
6776        strCorrectResponse = "f";
6777    }
6778
6779    aryData[AICC_INTERACTIONS_ID]               = strID;
6780    aryData[AICC_INTERACTIONS_RESPONSE]         = strResponse;
6781    aryData[AICC_INTERACTIONS_CORRECT]          = blnCorrect;
6782    aryData[AICC_INTERACTIONS_CORRECT_RESPONSE] = strCorrectResponse;
6783    aryData[AICC_INTERACTIONS_TIME_STAMP]       = dtmTime;
6784    aryData[AICC_INTERACTIONS_TYPE]             = AICC_INTERACTION_TYPE_TRUE_FALSE;
6785    aryData[AICC_INTERACTIONS_WEIGHTING]        = intWeighting;
6786    aryData[AICC_INTERACTIONS_LATENCY]          = intLatency;
6787    aryData[AICC_INTERACTIONS_RESPONSE_LONG]        = strResponse;
6788    aryData[AICC_INTERACTIONS_CORRECT_RESPONSE_LONG]= strCorrectResponse;
6789
6790
6791    //in AICC ignore the description field since there is nothing we can do with it
6792    //in AICC, don't try to set the interaction objective because these need to specified in the descriptor files
6793
6794    AICC_aryInteractions[intTotalInteractions] = aryData;
6795
6796    WriteToDebug("Added to interactions array, index=" + intTotalInteractions);
6797
6798    SetDirtyAICCData();
6799    return true;
6800}
6801
6802function AICC_RecordMultipleChoiceInteraction(strID, aryResponse, blnCorrect, aryCorrectResponse, strDescription, intWeighting, intLatency, strLearningObjectiveID, dtmTime){
6803
6804    WriteToDebug("In AICC_RecordMultipleChoiceInteraction strID=" + strID + ", aryResponse=" + aryResponse +
6805                    ", blnCorrect=" + blnCorrect + ", aryCorrectResponse=" + aryCorrectResponse +
6806                    ", strDescription=" + strDescription + ", intWeighting=" + intWeighting + ", intLatency=" + intLatency +
6807                    ", strLearningObjectiveID=" + strLearningObjectiveID + ", dtmTime=" + dtmTime);
6808
6809
6810    var intTotalInteractions;
6811    var aryData = new Array(10);
6812
6813    intTotalInteractions = AICC_aryInteractions.length;
6814
6815    if (intWeighting == null || intWeighting == undefined){intWeighting="";}
6816    if (intLatency == null || intLatency == undefined){intLatency="";}
6817    if (blnCorrect == null || blnCorrect == undefined){blnCorrect="";}
6818
6819
6820    var strResponse = "";
6821    var strResponseLong = "";
6822
6823    var strCorrectResponse = "";
6824    var strCorrectResponseLong = "";
6825
6826    if (aryResponse !== null) {
6827        for (var i=0; i < aryResponse.length; i++){
6828
6829            if (strResponse.length > 0) {strResponse += ",";}
6830            if (strResponseLong.length > 0) {strResponseLong += ",";}
6831
6832            strResponse += aryResponse[i].Short.replace(",", "");
6833            strResponseLong += aryResponse[i].Long.replace(",", "");
6834        }
6835    }
6836
6837    for (var i=0; i < aryCorrectResponse.length; i++){
6838
6839        if (strCorrectResponse.length > 0) {strCorrectResponse += ",";}
6840        if (strCorrectResponseLong.length > 0) {strCorrectResponseLong += ",";}
6841
6842        strCorrectResponse += aryCorrectResponse[i].Short.replace(",", "");
6843        strCorrectResponseLong += aryCorrectResponse[i].Long.replace(",", "");
6844    }
6845
6846    aryData[AICC_INTERACTIONS_ID]               = strID;
6847    aryData[AICC_INTERACTIONS_RESPONSE]         = strResponse;
6848    aryData[AICC_INTERACTIONS_CORRECT]          = blnCorrect;
6849    aryData[AICC_INTERACTIONS_CORRECT_RESPONSE] = strCorrectResponse;
6850    aryData[AICC_INTERACTIONS_TIME_STAMP]       = dtmTime;
6851    aryData[AICC_INTERACTIONS_TYPE]             = AICC_INTERACTION_TYPE_CHOICE;
6852    aryData[AICC_INTERACTIONS_WEIGHTING]        = intWeighting;
6853    aryData[AICC_INTERACTIONS_LATENCY]          = intLatency;
6854    aryData[AICC_INTERACTIONS_RESPONSE_LONG]        = strResponseLong;
6855    aryData[AICC_INTERACTIONS_CORRECT_RESPONSE_LONG]= strCorrectResponseLong;
6856
6857    //in AICC ignore the description field since there is nothing we can do with it
6858    //in AICC, don't try to set the interaction objective because these need to specified in the descriptor files
6859
6860    AICC_aryInteractions[intTotalInteractions] = aryData;
6861
6862    WriteToDebug("Added to interactions array, index=" + intTotalInteractions);
6863
6864    SetDirtyAICCData();
6865    return true;
6866}
6867
6868function AICC_RecordFillInInteraction(strID, strResponse, blnCorrect, strCorrectResponse, strDescription, intWeighting, intLatency, strLearningObjectiveID, dtmTime){
6869
6870    WriteToDebug("In AICC_RecordFillInInteraction strID=" + strID + ", strResponse=" + strResponse +
6871                    ", blnCorrect=" + blnCorrect + ", strCorrectResponse=" + strCorrectResponse +
6872                    ", strDescription=" + strDescription + ", intWeighting=" + intWeighting + ", intLatency=" + intLatency +
6873                    ", strLearningObjectiveID=" + strLearningObjectiveID + ", dtmTime=" + dtmTime);
6874
6875
6876    var intTotalInteractions;
6877    var aryData = new Array(10);
6878
6879    intTotalInteractions = AICC_aryInteractions.length;
6880
6881    if (intWeighting == null || intWeighting == undefined ){intWeighting="";}
6882    if (intLatency == null || intLatency == undefined){intLatency="";}
6883    if (blnCorrect == null || blnCorrect == undefined){blnCorrect="";}
6884    if (strResponse === null) {strResponse = "";}
6885    if (strCorrectResponse == null || strCorrectResponse == undefined){strCorrectResponse="";}
6886
6887    strResponse = new String(strResponse);
6888    if (strResponse.length > 255) {strResponse = strResponse.substr(0, 255);}
6889
6890    strCorrectResponse = new String(strCorrectResponse);
6891    if (strCorrectResponse.length > 255) {strCorrectResponse = strCorrectResponse.substr(0, 255);}
6892
6893    aryData[AICC_INTERACTIONS_ID]               = strID;
6894    aryData[AICC_INTERACTIONS_RESPONSE]         = strResponse;
6895    aryData[AICC_INTERACTIONS_CORRECT]          = blnCorrect;
6896    aryData[AICC_INTERACTIONS_CORRECT_RESPONSE] = strCorrectResponse;
6897    aryData[AICC_INTERACTIONS_TIME_STAMP]       = dtmTime;
6898    aryData[AICC_INTERACTIONS_TYPE]             = AICC_INTERACTION_TYPE_FILL_IN;
6899    aryData[AICC_INTERACTIONS_WEIGHTING]        = intWeighting;
6900    aryData[AICC_INTERACTIONS_LATENCY]          = intLatency;
6901    aryData[AICC_INTERACTIONS_RESPONSE_LONG]        = strResponse;
6902    aryData[AICC_INTERACTIONS_CORRECT_RESPONSE_LONG]= strCorrectResponse;
6903
6904    //in AICC ignore the description field since there is nothing we can do with it
6905    //in AICC, don't try to set the interaction objective because these need to specified in the descriptor files
6906
6907    AICC_aryInteractions[intTotalInteractions] = aryData;
6908
6909    WriteToDebug("Added to interactions array, index=" + intTotalInteractions);
6910
6911    SetDirtyAICCData();
6912    return true;
6913}
6914
6915function AICC_RecordMatchingInteraction(strID, aryResponse, blnCorrect, aryCorrectResponse, strDescription, intWeighting, intLatency, strLearningObjectiveID, dtmTime){
6916
6917    WriteToDebug("In AICC_RecordMatchingInteraction strID=" + strID + ", aryResponse=" + aryResponse +
6918                    ", blnCorrect=" + blnCorrect + ", aryCorrectResponse=" + aryCorrectResponse +
6919                    ", strDescription=" + strDescription + ", intWeighting=" + intWeighting + ", intLatency=" + intLatency +
6920                    ", strLearningObjectiveID=" + strLearningObjectiveID + ", dtmTime=" + dtmTime);
6921
6922
6923    var intTotalInteractions;
6924    var aryData = new Array(10);
6925
6926    intTotalInteractions = AICC_aryInteractions.length;
6927
6928    if (intWeighting == null || intWeighting == undefined ){intWeighting="";}
6929    if (intLatency == null || intLatency == undefined){intLatency="";}
6930    if (blnCorrect == null || blnCorrect == undefined){blnCorrect="";}
6931
6932
6933    var strResponse = "";
6934    var strResponseLong = "";
6935
6936    var strCorrectResponse = "";
6937    var strCorrectResponseLong = "";
6938
6939    if (aryResponse !== null) {
6940        for (var i=0; i < aryResponse.length; i++){
6941
6942            if (strResponse.length > 0) {strResponse += ",";}
6943            if (strResponseLong.length > 0) {strResponseLong += ",";}
6944
6945            strResponse += aryResponse[i].Source.Short.replace(",", "").replace(".", "") + "." + aryResponse[i].Target.Short.replace(",", "").replace(".", "");
6946            strResponseLong += aryResponse[i].Source.Long.replace(",", "").replace(".", "") + "." + aryResponse[i].Target.Long.replace(",", "").replace(".", "");
6947        }
6948    }
6949
6950    for (var i=0; i < aryCorrectResponse.length; i++){
6951
6952        if (strCorrectResponse.length > 0) {strCorrectResponse += ",";}
6953        if (strCorrectResponseLong.length > 0) {strCorrectResponseLong += ",";}
6954
6955        if (aryCorrectResponse[i].Source.Short != "" && aryCorrectResponse[i].Source.Long != ""){
6956            strCorrectResponse += aryCorrectResponse[i].Source.Short.replace(",", "").replace(".", "") + "." + aryCorrectResponse[i].Target.Short.replace(",", "").replace(".", "");
6957            strCorrectResponseLong += aryCorrectResponse[i].Source.Long.replace(",", "").replace(".", "") + "." + aryCorrectResponse[i].Target.Long.replace(",", "").replace(".", "");
6958        }
6959    }
6960
6961    aryData[AICC_INTERACTIONS_ID]               = strID;
6962    aryData[AICC_INTERACTIONS_RESPONSE]         = strResponse;
6963    aryData[AICC_INTERACTIONS_CORRECT]          = blnCorrect;
6964    aryData[AICC_INTERACTIONS_CORRECT_RESPONSE] = strCorrectResponse;
6965    aryData[AICC_INTERACTIONS_TIME_STAMP]       = dtmTime;
6966    aryData[AICC_INTERACTIONS_TYPE]             = AICC_INTERACTION_TYPE_MATCHING;
6967    aryData[AICC_INTERACTIONS_WEIGHTING]        = intWeighting;
6968    aryData[AICC_INTERACTIONS_LATENCY]          = intLatency;
6969    aryData[AICC_INTERACTIONS_RESPONSE_LONG]        = strResponseLong;
6970    aryData[AICC_INTERACTIONS_CORRECT_RESPONSE_LONG]= strCorrectResponseLong;
6971
6972    //in AICC ignore the description field since there is nothing we can do with it
6973    //in AICC, don't try to set the interaction objective because these need to specified in the descriptor files
6974
6975    AICC_aryInteractions[intTotalInteractions] = aryData;
6976
6977    WriteToDebug("Added to interactions array, index=" + intTotalInteractions);
6978
6979    SetDirtyAICCData();
6980    return true;
6981}
6982
6983function AICC_RecordPerformanceInteraction(strID, strResponse, blnCorrect, strCorrectResponse, strDescription, intWeighting, intLatency, strLearningObjectiveID, dtmTime){
6984
6985    WriteToDebug("In AICC_RecordPerformanceInteraction strID=" + strID + ", strResponse=" + strResponse +
6986                    ", blnCorrect=" + blnCorrect + ", strCorrectResponse=" + strCorrectResponse +
6987                    ", strDescription=" + strDescription + ", intWeighting=" + intWeighting + ", intLatency=" + intLatency +
6988                    ", strLearningObjectiveID=" + strLearningObjectiveID + ", dtmTime=" + dtmTime);
6989
6990
6991    var intTotalInteractions;
6992    var aryData = new Array(10);
6993
6994    intTotalInteractions = AICC_aryInteractions.length;
6995
6996    if (intWeighting == null || intWeighting == undefined ){intWeighting="";}
6997    if (intLatency == null || intLatency == undefined){intLatency="";}
6998    if (blnCorrect == null || blnCorrect == undefined){blnCorrect="";}
6999    if (strResponse === null) {strResponse = "";}
7000    if (strCorrectResponse == null || strCorrectResponse == undefined){strCorrectResponse="";}
7001
7002    strResponse = new String(strResponse);
7003    if (strResponse.length > 255) {strResponse = strResponse.substr(0, 255);}
7004
7005    strCorrectResponse = new String(strCorrectResponse);
7006    if (strCorrectResponse.length > 255) {strCorrectResponse = strCorrectResponse.substr(0, 255);}
7007
7008    aryData[AICC_INTERACTIONS_ID]               = strID;
7009    aryData[AICC_INTERACTIONS_RESPONSE]         = strResponse;
7010    aryData[AICC_INTERACTIONS_CORRECT]          = blnCorrect;
7011    aryData[AICC_INTERACTIONS_CORRECT_RESPONSE] = strCorrectResponse;
7012    aryData[AICC_INTERACTIONS_TIME_STAMP]       = dtmTime;
7013    aryData[AICC_INTERACTIONS_TYPE]             = AICC_INTERACTION_TYPE_PERFORMANCE;
7014    aryData[AICC_INTERACTIONS_WEIGHTING]        = intWeighting;
7015    aryData[AICC_INTERACTIONS_LATENCY]          = intLatency;
7016    aryData[AICC_INTERACTIONS_RESPONSE_LONG]        = strResponse;
7017    aryData[AICC_INTERACTIONS_CORRECT_RESPONSE_LONG]= strCorrectResponse;
7018
7019    //in AICC ignore the description field since there is nothing we can do with it
7020    //in AICC, don't try to set the interaction objective because these need to specified in the descriptor files
7021
7022    AICC_aryInteractions[intTotalInteractions] = aryData;
7023
7024    WriteToDebug("Added to interactions array, index=" + intTotalInteractions);
7025
7026    SetDirtyAICCData();
7027    return true;
7028}
7029
7030function AICC_RecordSequencingInteraction(strID, aryResponse, blnCorrect, aryCorrectResponse, strDescription, intWeighting, intLatency, strLearningObjectiveID, dtmTime){
7031
7032    WriteToDebug("In AICC_RecordSequencingInteraction strID=" + strID + ", aryResponse=" + aryResponse +
7033                    ", blnCorrect=" + blnCorrect + ", aryCorrectResponse=" + aryCorrectResponse +
7034                    ", strDescription=" + strDescription + ", intWeighting=" + intWeighting + ", intLatency=" + intLatency +
7035                    ", strLearningObjectiveID=" + strLearningObjectiveID + ", dtmTime=" + dtmTime);
7036
7037
7038    var intTotalInteractions;
7039    var aryData = new Array(10);
7040
7041    intTotalInteractions = AICC_aryInteractions.length;
7042
7043    if (intWeighting == null || intWeighting == undefined ){intWeighting="";}
7044    if (intLatency == null || intLatency == undefined){intLatency="";}
7045    if (blnCorrect == null || blnCorrect == undefined){blnCorrect="";}
7046
7047    var strResponse = "";
7048    var strResponseLong = "";
7049
7050    var strCorrectResponse = "";
7051    var strCorrectResponseLong = "";
7052
7053    if (aryResponse !== null) {
7054        for (var i=0; i < aryResponse.length; i++){
7055
7056            if (strResponse.length > 0) {strResponse += ",";}
7057            if (strResponseLong.length > 0) {strResponseLong += ",";}
7058
7059            strResponse += aryResponse[i].Short.replace(",", "");
7060            strResponseLong += aryResponse[i].Long.replace(",", "");
7061        }
7062    }
7063
7064    for (var i=0; i < aryCorrectResponse.length; i++){
7065
7066        if (strCorrectResponse.length > 0) {strCorrectResponse += ",";}
7067        if (strCorrectResponseLong.length > 0) {strCorrectResponseLong += ",";}
7068
7069        strCorrectResponse += aryCorrectResponse[i].Short.replace(",", "");
7070        strCorrectResponseLong += aryCorrectResponse[i].Long.replace(",", "");
7071    }
7072
7073    aryData[AICC_INTERACTIONS_ID]               = strID;
7074    aryData[AICC_INTERACTIONS_RESPONSE]         = strResponse;
7075    aryData[AICC_INTERACTIONS_CORRECT]          = blnCorrect;
7076    aryData[AICC_INTERACTIONS_CORRECT_RESPONSE] = strCorrectResponse;
7077    aryData[AICC_INTERACTIONS_TIME_STAMP]       = dtmTime;
7078    aryData[AICC_INTERACTIONS_TYPE]             = AICC_INTERACTION_TYPE_SEQUENCING;
7079    aryData[AICC_INTERACTIONS_WEIGHTING]        = intWeighting;
7080    aryData[AICC_INTERACTIONS_LATENCY]          = intLatency;
7081    aryData[AICC_INTERACTIONS_RESPONSE_LONG]        = strResponseLong;
7082    aryData[AICC_INTERACTIONS_CORRECT_RESPONSE_LONG]= strCorrectResponseLong;
7083
7084    //in AICC ignore the description field since there is nothing we can do with it
7085    //in AICC, don't try to set the interaction objective because these need to specified in the descriptor files
7086
7087    AICC_aryInteractions[intTotalInteractions] = aryData;
7088
7089    WriteToDebug("Added to interactions array, index=" + intTotalInteractions);
7090
7091    SetDirtyAICCData();
7092    return true;
7093}
7094
7095function AICC_RecordLikertInteraction(strID, response, blnCorrect, correctResponse, strDescription, intWeighting, intLatency, strLearningObjectiveID, dtmTime){
7096
7097    WriteToDebug("In RecordLikertInteraction strID=" + strID + ", response=" + response +
7098                    ", blnCorrect=" + blnCorrect + ", correctResponse=" + correctResponse +
7099                    ", strDescription=" + strDescription + ", intWeighting=" + intWeighting + ", intLatency=" + intLatency +
7100                    ", strLearningObjectiveID=" + strLearningObjectiveID + ", dtmTime=" + dtmTime);
7101
7102
7103    var intTotalInteractions;
7104    var aryData = new Array(10);
7105
7106    intTotalInteractions = AICC_aryInteractions.length;
7107
7108    if (intWeighting == null || intWeighting == undefined ){intWeighting="";}
7109    if (intLatency == null || intLatency == undefined){intLatency="";}
7110    if (blnCorrect == null || blnCorrect == undefined){blnCorrect="";}
7111
7112    var strResponse = "";
7113    var strResponseLong = "";
7114    if (response !== null) {
7115        strResponse = response.Short;
7116        strResponseLong = response.Long;
7117    }
7118
7119    var strCorrectResponse = "";
7120    var strCorrectResponseLong = "";
7121
7122    if (correctResponse != null){
7123        strCorrectResponse = correctResponse.Short;
7124        strCorrectResponseLong = correctResponse.Long;
7125    }
7126
7127    aryData[AICC_INTERACTIONS_ID]               = strID;
7128    aryData[AICC_INTERACTIONS_RESPONSE]         = strResponse;
7129    aryData[AICC_INTERACTIONS_CORRECT]          = blnCorrect;
7130    aryData[AICC_INTERACTIONS_CORRECT_RESPONSE] = strCorrectResponse;
7131    aryData[AICC_INTERACTIONS_TIME_STAMP]       = dtmTime;
7132    aryData[AICC_INTERACTIONS_TYPE]             = AICC_INTERACTION_TYPE_LIKERT;
7133    aryData[AICC_INTERACTIONS_WEIGHTING]        = intWeighting;
7134    aryData[AICC_INTERACTIONS_LATENCY]          = intLatency;
7135    aryData[AICC_INTERACTIONS_RESPONSE_LONG]        = strResponseLong;
7136    aryData[AICC_INTERACTIONS_CORRECT_RESPONSE_LONG]= strCorrectResponseLong;
7137
7138
7139    //in AICC ignore the description field since there is nothing we can do with it
7140    //in AICC, don't try to set the interaction objective because these need to specified in the descriptor files
7141
7142    AICC_aryInteractions[intTotalInteractions] = aryData;
7143
7144    WriteToDebug("Added to interactions array, index=" + intTotalInteractions);
7145
7146    SetDirtyAICCData();
7147    return true;
7148}
7149
7150function AICC_RecordNumericInteraction(strID, strResponse, blnCorrect, strCorrectResponse, strDescription, intWeighting, intLatency, strLearningObjectiveID, dtmTime){
7151
7152    WriteToDebug("In AICC_RecordNumericInteraction strID=" + strID + ", strResponse=" + strResponse +
7153                    ", blnCorrect=" + blnCorrect + ", strCorrectResponse=" + strCorrectResponse +
7154                    ", strDescription=" + strDescription + ", intWeighting=" + intWeighting + ", intLatency=" + intLatency +
7155                    ", strLearningObjectiveID=" + strLearningObjectiveID + ", dtmTime=" + dtmTime);
7156
7157
7158    var intTotalInteractions;
7159    var aryData = new Array(10);
7160
7161    intTotalInteractions = AICC_aryInteractions.length;
7162
7163    if (intWeighting == null || intWeighting == undefined ){intWeighting="";}
7164    if (intLatency == null || intLatency == undefined){intLatency="";}
7165    if (blnCorrect == null || blnCorrect == undefined){blnCorrect="";}
7166    if (strResponse === null) { strResponse = ""; }
7167
7168    if (strCorrectResponse != undefined && strCorrectResponse != null) {
7169        if (IsValidDecimalRange(strCorrectResponse))
7170        {
7171            //we could receive a decimal range correct response from SCORM 2004/Tin Can/Cmi5. If we do we need to convert that
7172            //to a decimal for use in AICC.
7173            strCorrectResponse = ConvertDecimalRangeToDecimalBasedOnLearnerResponse(strCorrectResponse, strResponse, blnCorrect);
7174        }
7175        //AICC does not accept decimal range correct answers, so if it's not a decimal by this point we fail
7176        if (!IsValidDecimal(strCorrectResponse)){
7177            WriteToDebug("Returning False - AICC_RecordNumericInteraction received invalid correct response (not a decimal), strCorrectResponse=" +strCorrectResponse);
7178            return false;
7179        }
7180    } else {
7181        strCorrectResponse = "";
7182    }
7183
7184    aryData[AICC_INTERACTIONS_ID]               = strID;
7185    aryData[AICC_INTERACTIONS_RESPONSE]         = strResponse;
7186    aryData[AICC_INTERACTIONS_CORRECT]          = blnCorrect;
7187    aryData[AICC_INTERACTIONS_CORRECT_RESPONSE] = strCorrectResponse;
7188    aryData[AICC_INTERACTIONS_TIME_STAMP]       = dtmTime;
7189    aryData[AICC_INTERACTIONS_TYPE]             = AICC_INTERACTION_TYPE_NUMERIC;
7190    aryData[AICC_INTERACTIONS_WEIGHTING]        = intWeighting;
7191    aryData[AICC_INTERACTIONS_LATENCY]          = intLatency;
7192    aryData[AICC_INTERACTIONS_RESPONSE_LONG]        = strResponse;
7193    aryData[AICC_INTERACTIONS_CORRECT_RESPONSE_LONG]= strCorrectResponse;
7194
7195    //in AICC ignore the description field since there is nothing we can do with it
7196    //in AICC, don't try to set the interaction objective because these need to specified in the descriptor files
7197
7198    AICC_aryInteractions[intTotalInteractions] = aryData;
7199
7200    WriteToDebug("Added to interactions array, index=" + intTotalInteractions);
7201
7202    SetDirtyAICCData();
7203    return true;
7204}
7205
7206function AICC_GetEntryMode(){
7207    WriteToDebug("In AICC_GetEntryMode, Returning " + AICC_Entry);
7208
7209    return AICC_Entry;
7210}
7211
7212function AICC_GetLessonMode(){
7213    WriteToDebug("In AICC_GetLessonMode, Returning " + AICC_strLessonMode);
7214
7215    return AICC_strLessonMode;
7216}
7217
7218function AICC_GetTakingForCredit(){
7219    WriteToDebug("In AICC_GetTakingForCredit, Returning " + AICC_blnCredit);
7220
7221    return AICC_blnCredit;
7222}
7223
7224
7225
7226function AICC_SetObjectiveScore(strObjectiveID, intScore, intMaxScore, intMinScore){
7227
7228    WriteToDebug("In AICC_SetObjectiveScore, strObjectiveID=" + strObjectiveID + ", intScore=" + intScore + ", intMaxScore=" + intMaxScore + ", intMinScore=" + intMinScore);
7229
7230    var intNextID;
7231    var intObjIndex;
7232    var strAICCScore = "";
7233
7234    intObjIndex = FindObjectiveById(strObjectiveID, AICC_aryObjectivesRead);
7235
7236    if (intObjIndex != null){
7237        WriteToDebug("Found read objective");
7238        AICC_aryObjectivesRead[intObjIndex][AICC_OBJ_ARRAY_SCORE
7238] = intScore;
7239    }
7240    else{
7241        WriteToDebug("Adding new read objective");
7242
7243        intNextID = AICC_aryObjectivesRead.length;
7244
7245        AICC_aryObjectivesRead[parseInt(intNextID, 10)] = new Array(3);
7246        AICC_aryObjectivesRead[parseInt(intNextID, 10)][AICC_OBJ_ARRAY_ID] = strObjectiveID;
7247        AICC_aryObjectivesRead[parseInt(intNextID, 10)][AICC_OBJ_ARRAY_SCORE] = intScore;
7248        AICC_aryObjectivesRead[parseInt(intNextID, 10)][AICC_OBJ_ARRAY_STATUS] = "";
7249    }
7250
7251
7252    intObjIndex = FindObjectiveById(strObjectiveID, AICC_aryObjectivesWrite);
7253
7254    if (intObjIndex != null){
7255        WriteToDebug("Found write objective");
7256        AICC_aryObjectivesWrite[intObjIndex][AICC_OBJ_ARRAY_SCORE] = intScore;
7257    }
7258    else{
7259        WriteToDebug("Adding new write objective");
7260
7261        intNextID = AICC_aryObjectivesWrite.length;
7262
7263        AICC_aryObjectivesWrite[parseInt(intNextID, 10)] = new Array(3);
7264
7265        strAICCScore = intScore;
7266
7267        //prior to version 3, AICC scores cannot contain a decimal
7268        if (AICC_LMS_Version < 3 && strAICCScore != ""){
7269            strAICCScore = parseInt(strAICCScore, 10);
7270        }
7271
7272        if (    (AICC_REPORT_MIN_MAX_SCORE === undefined ||
7273                 AICC_REPORT_MIN_MAX_SCORE === null ||
7274                 AICC_REPORT_MIN_MAX_SCORE === true) &&
7275            (AICC_LMS_Version >= 3 )        //min and max scores are only allowed after version 3
7276        ){
7277
7278            if ((intMaxScore != "") || (intMinScore != "")) {
7279                WriteToDebug("Appending Max and Min scores");
7280                strAICCScore += "," + intMaxScore + "," + intMinScore;
7281            }
7282
7283        }
7284
7285        AICC_aryObjectivesWrite[parseInt(intNextID, 10)][AICC_OBJ_ARRAY_ID] = strObjectiveID;
7286        AICC_aryObjectivesWrite[parseInt(intNextID, 10)][AICC_OBJ_ARRAY_SCORE
7286] = strAICCScore;
7287        AICC_aryObjectivesWrite[parseInt(intNextID, 10)][AICC_OBJ_ARRAY_STATUS] = "";
7288    }
7289
7290    SetDirtyAICCData();
7291
7292    return true;
7293}
7294
7295
7296function AICC_SetObjectiveStatus(strObjectiveID, Lesson_Status){
7297
7298    WriteToDebug("In AICC_SetObjectiveStatus, strObjectiveID=" + strObjectiveID + ", Lesson_Status=" + Lesson_Status);
7299
7300    var intNextID;
7301    var intObjIdex;
7302
7303    intObjIdex = FindObjectiveById(strObjectiveID, AICC_aryObjectivesRead);
7304
7305    if (intObjIdex != null){
7306        WriteToDebug("Found read objective");
7307        AICC_aryObjectivesRead[intObjIdex][AICC_OBJ_ARRAY_STATUS] = Lesson_Status;
7308    }
7309    else{
7310        WriteToDebug("Adding new read objective");
7311        intNextID = AICC_aryObjectivesRead.length;
7312
7313        AICC_aryObjectivesRead[parseInt(intNextID, 10)] = new Array(3);
7314
7315        AICC_aryObjectivesRead[parseInt(intNextID, 10)][AICC_OBJ_ARRAY_ID] = strObjectiveID;
7316        AICC_aryObjectivesRead[parseInt(intNextID, 10)][AICC_OBJ_ARRAY_STATUS] = Lesson_Status;
7317        AICC_aryObjectivesRead[parseInt(intNextID, 10)][AICC_OBJ_ARRAY_SCORE
7317] = "";
7318    }
7319
7320
7321    intObjIdex = FindObjectiveById(strObjectiveID, AICC_aryObjectivesWrite);
7322
7323    if (intObjIdex != null){
7324        WriteToDebug("Found write objective");
7325        AICC_aryObjectivesWrite[intObjIdex][AICC_OBJ_ARRAY_STATUS] = Lesson_Status;
7326    }
7327    else{
7328        WriteToDebug("Adding new write objective");
7329        intNextID = AICC_aryObjectivesWrite.length;
7330
7331        AICC_aryObjectivesWrite[parseInt(intNextID, 10)] = new Array(3);
7332
7333        AICC_aryObjectivesWrite[parseInt(intNextID, 10)][AICC_OBJ_ARRAY_ID] = strObjectiveID;
7334        AICC_aryObjectivesWrite[parseInt(intNextID, 10)][AICC_OBJ_ARRAY_STATUS] = Lesson_Status;
7335        AICC_aryObjectivesWrite[parseInt(intNextID, 10)][AICC_OBJ_ARRAY_SCORE] = "";
7336    }
7337
7338    SetDirtyAICCData();
7339
7340    return true;
7341}
7342
7343
7344function AICC_SetObjectiveDescription(strObjectiveID, strObjectiveDescription){
7345
7346    WriteToDebug("In AICC_SetObjectiveDescription, strObjectiveID=" + strObjectiveID + ", strObjectiveDescription=" + strObjectiveDescription);
7347    WriteToDebug("Objective descriptions are not supported prior to SCORM 2004");
7348
7349    return true;
7350
7351}
7352
7353
7354function AICC_GetObjectiveScore(strObjectiveID){
7355    WriteToDebug("In AICC_SetObjectiveScore, strObjectiveID=" + strObjectiveID);
7356
7357    var intObjIndex = FindObjectiveById(strObjectiveID, AICC_aryObjectivesRead)
7358
7359    if (intObjIndex != null){
7360        WriteToDebug("Found objective, returning " + AICC_aryObjectivesRead[intObjIndex][AICC_OBJ_ARRAY_SCORE
7360]);
7361        return AICC_aryObjectivesRead[intObjIndex][AICC_OBJ_ARRAY_SCORE];
7362    }
7363    else{
7364        WriteToDebug("Did not find objective, returning ''");
7365        return "";
7366    }
7367}
7368
7369function AICC_GetObjectiveDescription(strObjectiveID){
7370    WriteToDebug("In AICC_GetObjectiveDescription, strObjectiveID=" + strObjectiveID);
7371    WriteToDebug("Objective descriptions are not supported prior to SCORM 2004");
7372
7373    return "";
7374}
7375
7376function AICC_GetObjectiveStatus(strObjectiveID){
7377
7378    WriteToDebug("In AICC_SetObjectiveStatus, strObjectiveID=" + strObjectiveID);
7379
7380    var intObjIndex = FindObjectiveById(strObjectiveID, AICC_aryObjectivesRead)
7381
7382    if (intObjIndex != null){
7383        WriteToDebug("Found objective, returning " + AICC_aryObjectivesRead[intObjIndex][AICC_OBJ_ARRAY_STATUS]);
7384        return AICC_aryObjectivesRead[intObjIndex][AICC_OBJ_ARRAY_STATUS];
7385    }
7386    else{
7387        WriteToDebug("Did not find objective, returning " + LESSON_STATUS_NOT_ATTEMPTED);
7388        return LESSON_STATUS_NOT_ATTEMPTED;
7389    }
7390}
7391
7392function AICC_SetFailed(){
7393    WriteToDebug("In AICC_SetFailed, Returning true");
7394
7395    AICC_Status = LESSON_STATUS_FAILED;
7396
7397    SetDirtyAICCData();
7398
7399    return true;
7400}
7401
7402function AICC_SetPassed(){
7403    WriteToDebug("In AICC_SetPassed, Returning true");
7404
7405    AICC_Status = LESSON_STATUS_PASSED;
7406
7407    SetDirtyAICCData();
7408
7409    return true;
7410}
7411
7412function AICC_SetCompleted(){
7413    WriteToDebug("In AICC_SetCompleted, Returning true");
7414
7415    AICC_Status = LESSON_STATUS_COMPLETED;
7416
7417    SetDirtyAICCData();
7418
7419    return true;
7420}
7421
7422function AICC_ResetStatus(){
7423    WriteToDebug("In AICC_ResetStatus, Returning true");
7424
7425    AICC_Status = LESSON_STATUS_INCOMPLETE;
7426
7427    SetDirtyAICCData();
7428
7429    return true;
7430}
7431
7432function AICC_GetStatus(){
7433    WriteToDebug("In AICC_GetStatus, Returning " + AICC_Status);
7434
7435    return AICC_Status;
7436}
7437
7438//public
7439function AICC_GetProgressMeasure(){
7440    WriteToDebug("AICC_GetProgressMeasure - AICC does not support progress_measure, returning false");
7441    return false;
7442}
7443//public
7444function AICC_SetProgressMeasure(){
7445    WriteToDebug("AICC_SetProgressMeasure - AICC does not support progress_measure, returning false");
7446    return false;
7447}
7448
7449//public
7450function AICC_GetObjectiveProgressMeasure(){
7451    WriteToDebug("AICC_GetObjectiveProgressMeasure - AICC does not support progress_measure, returning false");
7452    return false;
7453}
7454//public
7455function AICC_SetObjectiveProgressMeasure(){
7456    WriteToDebug("AICC_SetObjectiveProgressMeasure - AICC does not support progress_measure, returning false");
7457    return false;
7458}
7459
7460function AICC_SetPointBasedScore(intScore, intMaxScore, intMinScore){
7461    WriteToDebug("AICC_SetPointBasedScore - AICC does not support SetPointBasedScore, falling back to SetScore");
7462    return AICC_SetScore(intScore, intMaxScore, intMinScore);
7463}
7464
7465function AICC_GetScaledScore(intScore, intMaxScore, intMinScore){
7466    WriteToDebug("AICC_GetScaledScore - AICC does not support GetScaledScore, returning false");
7467    return false;
7468}
7469
7470function AICC_GetLastError(){
7471    WriteToDebug("In AICC_GetLastError, Returning " + intAICCErrorNum);
7472
7473    return intAICCErrorNum;
7474}
7475
7476function AICC_GetLastErrorDesc(){
7477    WriteToDebug("In AICC_GetLastErrorDesc, Returning '" + strAICCErrorDesc + "'");
7478
7479    return strAICCErrorDesc;
7480}
7481
7482
7483
7484//==============================================================================
7485
7486
7487function AICC_PutParamFailed(){
7488    WriteToDebug("ERROR: In AICC_PutParamFailed");
7489
7490    //set dirty data
7491    SetDirtyAICCData();
7492
7493    //currently, just leaves the data as dirty, could also re-try or display an error as desired
7494
7495    return;
7496}
7497
7498function AICC_PutInteractionsFailed(){
7499    WriteToDebug("ERROR: In AICC_PutInteractionsFailed");
7500
7501    //set dirty data
7502    SetDirtyAICCData();
7503
7504    //the LMS probably only accepts strictly conformant interaction result values, so retry one more time
7505    //with the short values which will be used from now on
7506
7507    //this call originates from the child frame so be sure to reference the parent
7508    if (parent.blnUseLongInteractionResultValues == true){
7509
7510        parent.blnUseLongInteractionResultValues = false;
7511
7512        parent.AICC_CommitData();
7513    }
7514
7515    return;
7516}
7517
7518function AICC_SetErrorInfo(strErrorNumLine, strErrorDescLine){
7519    WriteToDebug("ERROR: In AICC_SetErrorInfo, strErrorNumLine=" + strErrorNumLine + ", strErrorDescLine=" + strErrorDescLine);
7520
7521    if (strErrorNumLine.toLowerCase().search(/error\s*=\s*0/) == -1){
7522        WriteToDebug("Detected No Error");
7523        intAICCErrorNum = NO_ERROR;
7524        strAICCErrorDesc = "";
7525    }
7526    else{
7527        WriteToDebug("Setting Error Info");
7528        AICC_SetError(GetValueFromAICCLine(strAICCErrorLine), GetValueFromAICCLine(strAICCErrorDesc))
7529    }
7530}
7531
7532function AICC_SetError(intErrorNum, strErrorDesc){
7533    WriteToDebug("ERROR: In AICC_SetError, intErrorNum=" + intErrorNum + ", strErrorDesc=" + strErrorDesc);
7534
7535    intAICCErrorNum = intErrorNum;
7536    strAICCErrorDesc = strAICCErrorDesc;
7537}
7538
7539function SetDirtyAICCData(){
7540    WriteToDebug("In SetDirtyAICCData");
7541    blnDirtyAICCData = true;
7542}
7543
7544function ClearDirtyAICCData(){
7545    WriteToDebug("In ClearDirtyAICCData");
7546    blnDirtyAICCData = false;
7547}
7548
7549function IsThereDirtyAICCData(){
7550    WriteToDebug("In IsThereDirtyAICCData, returning " + blnDirtyAICCData);
7551    return blnDirtyAICCData;
7552}
7553
7554
7555function GetValueFromAICCLine(strLine){
7556
7557    WriteToDebug("In GetValueFromAICCLine, strLine=" + strLine);
7558
7559    //find equal sign
7560    //if found equal sign
7561        //get all characters after equal sign
7562        //remove leading and trailing white space (cr, lf, tab, space, etc)
7563
7564    var intPos;
7565    var strValue = "";
7566    var strTemp;
7567
7568    strLine = new String(strLine);
7569
7570    intPos = strLine.indexOf("=");
7571
7572    WriteToDebug("intPos=" + intPos);
7573
7574    if (intPos > -1 && ((intPos + 1) < strLine.length)){
7575
7576        WriteToDebug("Grabbing value");
7577
7578        strTemp = strLine.substring(intPos+1);
7579
7580        WriteToDebug("strTemp=" + strTemp);
7581
7582        strTemp = strTemp.replace(/^\s*/, "");      //replace leading whitespace
7583        strTemp = strTemp.replace(/\s*$/, "");      //replace trailing whitespace
7584
7585        strValue = strTemp;
7586    }
7587
7588    WriteToDebug("returning " + strValue);
7589
7590    return strValue;
7591
7592}
7593
7594function GetNameFromAICCLine(strLine){
7595    //find equal sign
7596    //if found equal sign
7597        //get all characters after equal sign
7598        //remove leading and trailing white space (cr, lf, tab, space, etc)
7599    //else, look for brackets and return contents of brackets
7600
7601    WriteToDebug("In GetNameFromAICCLine, strLine=" + strLine);
7602
7603    var intPos;
7604    var strTemp;
7605
7606    var strName = "";
7607
7608    strLine = new String(strLine);
7609
7610    intPos = strLine.indexOf("=");
7611
7612    WriteToDebug("intPos=" + intPos);
7613
7614    if (intPos > -1 && intPos < strLine.length){
7615
7616        WriteToDebug("Grabbing name from name/value pair");
7617
7618        strTemp = strLine.substring(0, intPos);
7619
7620        WriteToDebug("strTemp=" + strTemp);
7621
7622        strTemp = strTemp.replace(/^\s*/, "");
7623        strTemp = strTemp.replace(/\s*$/, "");
7624
7625        strName = strTemp;
7626    }
7627
7628    else{
7629
7630        WriteToDebug("Grabbing name from group / section heading");
7631
7632        intPos = strLine.indexOf("[");
7633
7634        WriteToDebug("intPos=" + intPos);
7635
7636        if (intPos > -1){
7637
7638            WriteToDebug("Replacing []");
7639
7640            strTemp = strLine.replace(/[\[|\]]/g, "");  //replace the square brackets
7641
7642            WriteToDebug("strTemp=" + strTemp);
7643
7644            strTemp = strTemp.replace(/^\s*/, "");      //replace leading whitespace
7645            strTemp = strTemp.replace(/\s*$/, "");      //replace trailing whitespace
7646
7647            strName = strTemp;
7648        }
7649
7650    }
7651
7652
7653    WriteToDebug("returning " + strName);
7654
7655    return strName;
7656}
7657
7658
7659
7660function GetIndexFromAICCName(strLineName){
7661
7662    WriteToDebug("In GetIndexFromAICCName, strLineName=" + strLineName);
7663
7664    //find period
7665        //if found period
7666        //get all characters after preiod
7667        //if there is an equal sign (we got an entire line instead of a line name
7668            //remove all characters after the equal sign
7669        //remove leading and trailing white space (cr, lf, tab, space, etc)
7670
7671    var intPos;
7672    var strIndex = "";
7673    var strTemp = "";
7674
7675    strLine = new String(strLineName);
7676
7677    intPos = strLine.indexOf(".");
7678
7679    WriteToDebug("intPos=" + intPos);
7680
7681    if (intPos > -1 && (intPos+1) < strLine.length){
7682
7683        WriteToDebug("Grabbing index");
7684
7685        strTemp = strLine.substring(intPos + 1);
7686
7687
7688        WriteToDebug("strTemp=" + strTemp);
7689
7690        WriteToDebug("Checking for equal sign");
7691
7692        intPos = strTemp.indexOf("=");
7693
7694        if (intPos > -1 && intPos < strTemp.length){
7695            WriteToDebug("Found and removing equal sign");
7696            strTemp = strLine.substring(0, intPos);
7697        }
7698
7699        WriteToDebug("Removing white space");
7700
7701        strTemp = strTemp.replace(/^\s*/, "");      //replace leading whitespace
7702        strTemp = strTemp.replace(/\s*$/, "");      //replace trailing whitespace
7703
7704        strIndex = strTemp;
7705    }
7706
7707    WriteToDebug("returning " + strIndex);
7708
7709    return strIndex;
7710}
7711
7712
7713//==============================================================================
7714
7715function ParseGetParamData(strLMSResult){
7716
7717    WriteToDebug("In ParseGetParamData");
7718
7719    var aryAICCResponseLines;
7720    var strLine;
7721    var strLineName;
7722    var strLineValue;
7723    var i, j;           //loop line counters
7724
7725    //parse LMS Result into local variables
7726
7727    strLMSResult = new String(strLMSResult);
7728    aryAICCResponseLines = strLMSResult.split("\n");    //only use \n instead of \r\n b/c some LMS's will only use one character
7729
7730    WriteToDebug("Split String");
7731
7732    for (i=0; i < aryAICCResponseLines.length; i++){
7733
7734        WriteToDebug("Processing Line #" + i + ": " + aryAICCResponseLines[i]);
7735
7736        strLine = aryAICCResponseLines[i];
7737
7738        strLineName = "";
7739        strLineValue = "";
7740
7741        if (strLine.length > 0){
7742
7743            WriteToDebug("Found non-zero length string");
7744
7745            //remove \r from the beginning or end of the string in case we missed it in the original array split
7746            if (strLine.charAt(0) == "\r"){
7747                WriteToDebug("Detected leading \\r");
7748                strLine = strLine.substr(1);
7749            }
7750            if (strLine.charAt(strLine.length - 1) == "\r"){
7751                WriteToDebug("Detected trailing \\r");
7752                strLine = strLine.substr(0, strLine.length - 1);
7753            }
7754
7755            if (strLine.charAt(0) != ";") {             //semi-colon indicates a comment line, ignore these
7756                WriteToDebug("Found non-comment line");
7757
7758                strLineName  = GetNameFromAICCLine(strLine);
7759                strLineValue = GetValueFromAICCLine(strLine);
7760
7761                WriteToDebug("strLineName=" + strLineName + ", strLineValue=" + strLineValue);
7762            }
7763        }
7764
7765        strLineName = strLineName.toLowerCase();
7766
7767        if (! AICC_HasItemBeenFound(strLineName)){      //only process an item the first time it is found since only the first instance is significant
7768
7769            WriteToDebug("Detected an un-found item");
7770
7771            AICC_FoundItem(strLineName);
7772
7773            switch (strLineName){
7774
7775                case "version":             //version
7776                    WriteToDebug("Item is version");
7777                    var tempVersion = parseFloat(strLineValue);
7778
7779                    if (isNaN(tempVersion)){tempVersion=0;}
7780
7781                    AICC_LMS_Version = tempVersion;
7782
7783                break;
7784
7785                case "student_id":              //student id
7786                    WriteToDebug("Item is student_id");
7787                    AICC_Student_ID = strLineValue;
7788                break;
7789
7790                case "student_name":            //student name
7791                    WriteToDebug("Item is student_name");
7792                    AICC_Student_Name = strLineValue;
7793                break;
7794
7795                case "lesson_location":         //bookmark
7796                    WriteToDebug("Item is lesson_location");
7797                    AICC_Lesson_Location = strLineValue;
7798                break;
7799
7800                case "score":                   //score
7801                    WriteToDebug("Item is score");
7802                    AICC_Score = strLineValue;
7803
7804                    AICC_SeperateScoreValues(AICC_Score);
7805
7806                break;
7807
7808                case "credit":                  //credit
7809                    WriteToDebug("Item is credit");
7810                    AICC_Credit = strLineValue;
7811
7812                    AICC_TranslateCredit(AICC_Credit);
7813
7814                break;
7815
7816                case "lesson_status":           //status, flag for entry mode
7817                    WriteToDebug("Item is lesson_status");
7818                    AICC_Lesson_Status = strLineValue;
7819
7820                    AICC_TranslateLessonStatus(AICC_Lesson_Status);
7821
7822                break;
7823
7824                case "time":                    //previous time
7825                    WriteToDebug("Item is time");
7826                    AICC_Time = strLineValue;
7827
7828                    AICC_TranslateTimeToMilliseconds(AICC_Time);
7829
7830                break;
7831
7832                case "mastery_score":           //passing score
7833                    WriteToDebug("Item is mastery_score");
7834                    AICC_Mastery_Score = strLineValue;
7835
7836                    AICC_ValidateMasteryScore(AICC_Mastery_Score);
7837                break;
7838
7839                case "lesson_mode":             //lesson mode (browse, normal, review)
7840                    WriteToDebug("Item is lesson_mode");
7841                    AICC_Lesson_Mode = strLineValue;
7842
7843                    AICC_TranslateLessonMode(AICC_Lesson_Mode);
7844
7845                break;
7846
7847                case "max_time_allowed":        //max time allowed
7848                    WriteToDebug("Item is max_time_allowed");
7849                    AICC_Max_Time_Allowed = strLineValue;
7850
7851                    AICC_TranslateMaxTimeToMilliseconds(AICC_Max_Time_Allowed);
7852
7853                break;
7854
7855                case "time_limit_action":       //display message on timeout, exit on timeout
7856                    WriteToDebug("Item is time_limit_action");
7857                    AICC_Time_Limit_Action = strLineValue;
7858
7859                    AICC_TranslateTimeLimitAction(AICC_Time_Limit_Action);
7860
7861                break;
7862
7863                case "audio":                   //audio play, audio speed
7864                    WriteToDebug("Item is audio");
7865                    AICC_Audio = strLineValue;
7866
7867                    AICC_TranslateAudio(AICC_Audio);
7868
7869                break;
7870
7871                case "speed":                   //content speed
7872                    WriteToDebug("Item is speed");
7873                    AICC_Speed = strLineValue;
7874
7875                    AICC_TranslateSpeed(AICC_Speed);
7876
7877                break;
7878
7879                case "language":                //language
7880                    WriteToDebug("Item is language");
7881                    AICC_Language = strLineValue;
7882
7883                break;
7884
7885                case "text":                    //text
7886                    WriteToDebug("Item is text");
7887                    AICC_Text = strLineValue;
7888
7889                    AICC_TranslateTextPreference(AICC_Text);
7890
7891                break;
7892
7893                case "course_id":               //course id
7894                    WriteToDebug("Item is course id");
7895                    AICC_CourseID = strLineValue;
7896
7897                break;
7898
7899                case "core_vendor":             //launch data
7900                    WriteToDebug("Item is core_vendor");
7901
7902                    AICC_Launch_Data = "";
7903
7904                    strLine = "";
7905                    j=1;
7906
7907                    if ((i+j) < aryAICCResponseLines.length){
7908                        strLine = aryAICCResponseLines[i+j];
7909                    }
7910
7911                    //loop to the end of the file or current group
7912                    while ( ((i+j) < aryAICCResponseLines.length) && (! IsGroupIdentifier(strLine))){
7913
7914                        if (strLine.charAt(0) != ";"){
7915                            AICC_Launch_Data += strLine + "\n";     //add \n to make up for the one we dropped when splitting the string into the array
7916                        }
7917
7918                        j = j + 1;
7919
7920                        if ((i+j) < aryAICCResponseLines.length){
7921                            strLine = aryAICCResponseLines[i+j];
7922                        }
7923                    }
7924
7925                    i = i + j - 1
7926
7927                    AICC_Launch_Data = AICC_Launch_Data.replace(/\s*$/, "");        //replace trailing whitespace (we've added an extra \n to the end of the string)
7928
7929                break;
7930
7931                case "core_lesson":             //data chunk
7932                    WriteToDebug("Item is core_lesson");
7933
7934                    AICC_Data_Chunk = "";
7935
7936                    strLine = "";
7937                    j=1;
7938
7939                    if ((i+j) < aryAICCResponseLines.length){
7940                        strLine = aryAICCResponseLines[i+j];
7941                    }
7942
7943                    //loop to the end of the file or current group
7944                    while ( ((i+j) < aryAICCResponseLines.length) && (! IsGroupIdentifier(strLine))){
7945
7946                        if (strLine.charAt(0) != ";"){
7947                            AICC_Data_Chunk += strLine + "\n";      //add \n to make up for the one we dropped when splitting the string into the array
7948                        }
7949
7950                        j = j + 1;
7951
7952                        if ((i+j) < aryAICCResponseLines.length){
7953                            strLine = aryAICCResponseLines[i+j];
7954                        }
7955                    }
7956
7957                    i = i + j - 1
7958
7959                    AICC_Data_Chunk = AICC_Data_Chunk.replace(/\s*$/, "");      //replace trailing whitespace (we've added an extra \n to the end of the string)
7960                break;
7961
7962                case "comments":                //comments from LMS
7963                    WriteToDebug("Item is comments");
7964
7965                    AICC_Comments = "";
7966
7967                    strLine = "";
7968                    j=1;
7969
7970                    if ((i+j) < aryAICCResponseLines.length){
7971                        strLine = aryAICCResponseLines[i+j];
7972                    }
7973
7974                    //loop to the end of the file or current group
7975                    while ( ((i+j) < aryAICCResponseLines.length) && (! IsGroupIdentifier(strLine))){
7976
7977                        if (strLine.charAt(0) != ";"){
7978                            AICC_Comments += strLine + "\n";        //add \n to make up for the one we dropped when splitting the string into the array
7979                        }
7980
7981                        j = j + 1;
7982
7983                        if ((i+j) < aryAICCResponseLines.length){
7984                            strLine = aryAICCResponseLines[i+j];
7985                        }
7986                    }
7987
7988                    i = i + j - 1
7989
7990                    AICC_Comments = AICC_Comments.replace(/\s*$/, "");      //replace trailing whitespace (we've added an extra \n to the end of the string)
7991
7992                break;
7993
7994                case "objectives_status":       //objectives
7995                    WriteToDebug("Item is objectives_status");
7996
7997                    AICC_Objectives = "";
7998
7999                    strLine = "";
8000                    j=1;
8001
8002                    if ((i+j) < aryAICCResponseLines.length){
8003                        strLine = aryAICCResponseLines[i+j];
8004                    }
8005
8006                    //loop to the end of the file or current group
8007                    while ( ((i+j) < aryAICCResponseLines.length) && (! IsGroupIdentifier(strLine))){
8008
8009                        if (strLine.charAt(0) != ";"){
8010                            AICC_Objectives += strLine + "\n";      //add \n to make up for the one we dropped when splitting the string into the array
8011                        }
8012
8013                        j = j + 1;
8014
8015                        if ((i+j) < aryAICCResponseLines.length){
8016                            strLine = aryAICCResponseLines[i+j];
8017                        }
8018                    }
8019
8020                    i = i + j - 1
8021                    AICC_Objectives = AICC_Objectives.replace(/\s*$/, "");      //replace trailing whitespace (we've added an extra \r\n to the end of the string)
8022
8023                    AICC_FormatObjectives(AICC_Objectives);
8024
8025                break;
8026
8027                default:
8028                    //comment or empty line so do nothing
8029                    WriteToDebug("Unknown Item Found");
8030                break;
8031
8032            }   //end switch
8033        }   //end if AICC_HasItemBeenFound
8034    }   //end for
8035
8036
8037    return true;
8038
8039}
8040
8041
8042function IsGroupIdentifier(strLine){
8043    WriteToDebug("In IsGroupIdentifier, strLine=" + strLine);
8044
8045    var intPos;
8046
8047    //remove leading white space
8048    strLine = strLine.replace(/^\s*/, "");
8049
8050    intPos = strLine.search(/\[[\w]+\]/);
8051
8052    WriteToDebug("intPos=" + intPos);
8053
8054    if (intPos == 0){
8055        WriteToDebug("Returning True");
8056        return true;
8057    }
8058    else{
8059        WriteToDebug("Returning False");
8060        return false;
8061    }
8062}
8063
8064function AICC_FoundItem(strItem){
8065    WriteToDebug("In AICC_FoundItem, strItem=" + strItem);
8066    aryAICCFoundItems[strItem] = true;
8067}
8068
8069function AICC_HasItemBeenFound(strItem){
8070    WriteToDebug("In AICC_HasItemBeenFound, strItem=" + strItem);
8071
8072    if (aryAICCFoundItems[strItem] == true){
8073        WriteToDebug("Returning True");
8074        return true;
8075    }
8076    else{
8077        WriteToDebug("Returning False");
8078        return false;
8079    }
8080}
8081
8082
8083
8084//==================================================================
8085//functions for dealing with specific data elements
8086//==================================================================
8087function AICC_SeperateScoreValues(AICC_Score){
8088    WriteToDebug("In AICC_SeperateScoreValues, AICC_Score=" + AICC_Score);
8089
8090    var aryScores;
8091    aryScore = AICC_Score.split(",");
8092
8093    AICC_fltScoreRaw = aryScore[0];
8094
8095    if (IsValidDecimal(AICC_fltScoreRaw)){
8096        WriteToDebug("Found a valid decimal");
8097        AICC_fltScoreRaw = parseFloat(AICC_fltScoreRaw);
8098    }
8099    else{
8100        WriteToDebug("ERROR - score from LMS is not a valid decimal");
8101        AICC_SetError(AICC_ERROR_INVALID_DECIMAL, "score is not a valid decimal")
8102    }
8103
8104    if (aryScore.length > 1){
8105        WriteToDebug("Max score found");
8106        AICC_fltScoreMax = aryScore[1];
8107        if ( IsValidDecimal(AICC_fltScoreMax)){
8108            WriteToDebug("Found a valid decimal");
8109            AICC_fltScoreMax = parseFloat(AICC_fltScoreMax);
8110        }
8111        else{
8112            WriteToDebug("ERROR - max score from LMS is not a valid decimal");
8113            AICC_SetError(AICC_ERROR_INVALID_DECIMAL, "max score is not a valid decimal")
8114        }
8115    }
8116
8117    if (aryScore.length > 2){
8118        WriteToDebug("Max score found");
8119        AICC_fltScoreMin = aryScore[2];
8120        if (IsValidDecimal(AICC_fltScoreMin)){
8121            WriteToDebug("Found a valid decimal");
8122            AICC_fltScoreMin = parseFloat(AICC_fltScoreMin);
8123        }
8124        else{
8125            WriteToDebug("ERROR - min score from LMS is not a valid decimal");
8126            AICC_SetError(AICC_ERROR_INVALID_DECIMAL, "min score is not a valid decimal")
8127        }
8128
8129    }
8130
8131}
8132
8133
8134function AICC_ValidateMasteryScore(strScore){
8135    WriteToDebug("In AICC_ValidateMasteryScore, strScore=" + strScore);
8136
8137    if (IsValidDecimal(strScore)){
8138        AICC_Mastery_Score = parseFloat(strScore);
8139    }
8140    else{
8141        WriteToDebug("ERROR - mastery score from LMS is not a valid decimal");
8142        AICC_SetError(AICC_ERROR_INVALID_DECIMAL, "mastery score is not a valid decimal")
8143    }
8144
8145}
8146
8147function AICC_TranslateCredit(strCredit){
8148    WriteToDebug("In AICC_TranslateCredit, strCredit=" + strCredit);
8149
8150    var strFirstChar;
8151
8152    strFirstChar = strCredit.toLowerCase().charAt(0);
8153
8154    if (strFirstChar == "c"){
8155        WriteToDebug("Credit = true");
8156        AICC_blnCredit = true;
8157    }
8158    else if (strFirstChar == "n"){
8159        WriteToDebug("Credit = false");
8160        AICC_blnCredit = false
8161    }
8162    else{
8163        WriteToDebug("ERROR - credit value from LMS is not a valid");
8164        AICC_SetError(AICC_ERROR_INVALID_CREDIT, "credit value from LMS is not a valid")
8165    }
8166
8167}
8168
8169function AICC_TranslateLessonMode(strMode){
8170    WriteToDebug("In AICC_TranslateLessonMode, strMode=" + strMode);
8171    var strFirstChar;
8172
8173    strFirstChar = strMode.toLowerCase().charAt(0);
8174
8175    if (strFirstChar == "b"){
8176        WriteToDebug("Lesson Mode = Browse");
8177        AICC_strLessonMode = MODE_BROWSE;
8178    }
8179    else if (strFirstChar == "n"){
8180        WriteToDebug("Lesson Mode = normal");
8181        AICC_strLessonMode = MODE_NORMAL;
8182    }
8183    else if (strFirstChar == "r"){
8184        WriteToDebug("Lesson Mode = review");
8185        AICC_strLessonMode = MODE_REVIEW;
8186
8187        if (!(typeof(REVIEW_MODE_IS_READ_ONLY) == "undefined") && REVIEW_MODE_IS_READ_ONLY === true){
8188            blnReviewModeSoReadOnly = true;
8189        }
8190
8191    }
8192    else{
8193        WriteToDebug("ERROR - lesson_mode value from LMS is not a valid");
8194        AICC_SetError(AICC_ERROR_INVALID_LESSON_MODE, "lesson_mode value from LMS is not a valid")
8195
8196    }
8197}
8198
8199
8200function AICC_TranslateTimeToMilliseconds(strCMITime){
8201    WriteToDebug("In AICC_TranslateTimeToMilliseconds, strCMITime=" + strCMITime);
8202
8203    if (IsValidCMITimeSpan(strCMITime)){
8204        AICC_intPreviouslyAccumulatedMilliseconds = ConvertCMITimeSpanToMS(strCMITime);
8205    }
8206    else{
8207        WriteToDebug("ERROR - Invalid CMITimeSpan");
8208        AICC_SetError(AICC_ERROR_INVALID_TIMESPAN, "Invalid timespan (previously accumulated time) received from LMS");
8209    }
8210
8211}
8212
8213function AICC_TranslateMaxTimeToMilliseconds(strCMITime){
8214    WriteToDebug("In AICC_TranslateMaxTimeToMilliseconds, strCMITime=" + strCMITime);
8215
8216    if (IsValidCMITimeSpan(strCMITime)){
8217        AICC_intMaxTimeAllowedMilliseconds = ConvertCMITimeSpanToMS(strCMITime);
8218    }
8219    else{
8220        WriteToDebug("ERROR - Invalid CMITimeSpan");
8221        AICC_SetError(AICC_ERROR_INVALID_TIMESPAN, "Invalid timespan (max time allowed) received from LMS");
8222    }
8223
8224}
8225
8226function AICC_TranslateTimeLimitAction(strTimeLimitAction){
8227    WriteToDebug("In AICC_TranslateTimeLimitAction, strTimeLimitAction=" + strTimeLimitAction);
8228
8229    var arySplit;
8230    var blnError = false;
8231    var strChar1 = "";
8232    var strChar2 = "";
8233
8234    //note - order of characters is not significant
8235
8236    arySplit = strTimeLimitAction.split(",");
8237
8238    if (arySplit.length == 2){
8239
8240        WriteToDebug("Found 2 elements");
8241
8242        strChar1 = arySplit[0].charAt(0).toLowerCase();
8243        strChar2 = arySplit[1].charAt(0).toLowerCase();
8244
8245        WriteToDebug("Got characters, strChar1=" + strChar1 + ", strChar2=" + strChar2);
8246
8247        if ( (strChar1 != "e" && strChar1 != "c" && strChar1 != "m" && strChar1 != "n") ||
8248             (strChar2 != "e" && strChar2 != "c" && strChar2 != "m" && strChar2 != "n") ||
8249             (strChar1 == strChar2)
8250           ) {
8251                blnError = true
8252                WriteToDebug("Found an invalid character, or 2 identical characters");
8253           }
8254
8255        if (strChar1 == "e" || strChar2 == "e") {AICC_blnExitOnTimeout = true;}
8256        if (strChar1 == "c" || strChar2 == "c") {AICC_blnExitOnTimeout = false;}
8257
8258        if (strChar1 == "n" || strChar2 == "n") {AICC_blnShowMessageOnTimeout = false;}
8259        if (strChar1 == "m" || strChar2 == "m") {AICC_blnShowMessageOnTimeout = true;}
8260
8261        WriteToDebug("AICC_blnExitOnTimeout=" + AICC_blnExitOnTimeout + ", AICC_blnShowMessageOnTimeout" + AICC_blnShowMessageOnTimeout);
8262    }
8263    else{
8264        WriteToDebug("Line does not contain two comma-delimited elements");
8265        blnError = true;
8266    }
8267
8268    if (blnError){
8269        WriteToDebug("ERROR - Invalid Time Limit Action");
8270        AICC_SetError(AICC_ERROR_INVALID_TIME_LIMIT_ACTION, "Invalid time limit action received from LMS");
8271
8272    }
8273
8274}
8275
8276
8277function AICC_TranslateTextPreference(strPreference){
8278    WriteToDebug("In AICC_TranslateTextPreference, strPreference=" + strPreference);
8279
8280    if (strPreference == -1){
8281        WriteToDebug("Text Preference = off");
8282        AICC_TextPreference = PREFERENCE_OFF;
8283    }
8284    else if (strPreference == 0){
8285        WriteToDebug("Text Preference = default");
8286        AICC_TextPreference = PREFERENCE_DEFAULT;
8287    }
8288    else if (strPreference == 1){
8289        WriteToDebug("Text Preference = on");
8290        AICC_TextPreference = PREFERENCE_ON;
8291    }
8292    else{
8293        WriteToDebug("ERROR - Invalid Text Preference");
8294        AICC_SetError(AICC_ERROR_INVALID_PREFERENCE, "Invalid Text Preference received from LMS");
8295
8296    }
8297}
8298
8299
8300function AICC_TranslateLessonStatus(strStatus){
8301    WriteToDebug("In AICC_TranslateLessonStatus, strStatus=" + strStatus);
8302
8303    var strFirstChar;
8304    var intPos;
8305    var strEntry;
8306
8307    strFirstChar = strStatus.charAt(0).toLowerCase();
8308
8309    AICC_Status = AICC_ConvertAICCStatusIntoLocalStatus(strFirstChar);
8310
8311    WriteToDebug("AICC_Status=" + AICC_Status);
8312
8313    //check for an entry flag
8314    intPos = strStatus.indexOf(",");
8315    if (intPos > 0){
8316        strEntry = strStatus.substr(intPos);
8317        strEntry = strEntry.replace(/,/, "");
8318
8319        strFirstChar = strEntry.charAt(0).toLowerCase();
8320
8321        if (strFirstChar == "a"){
8322            WriteToDebug("Entry is Ab initio");
8323            AICC_Entry = ENTRY_FIRST_TIME;
8324        }
8325        else if (strFirstChar == "r"){
8326            WriteToDebug("Entry is Resume");
8327            AICC_Entry = ENTRY_RESUME;
8328        }
8329        else{
8330            WriteToDebug("ERROR - entry not found");
8331            AICC_SetError(AICC_ERROR_INVALID_ENTRY, "Invalid lesson status received from LMS");
8332        }
8333    }
8334
8335}
8336
8337
8338function AICC_ConvertAICCStatusIntoLocalStatus(strFirstCharOfAICCStatus){
8339    WriteToDebug("In AICC_ConvertAICCStatusIntoLocalStatus, strFirstCharOfAICCStatus=" + strFirstCharOfAICCStatus);
8340
8341    if (strFirstCharOfAICCStatus == "p"){
8342        WriteToDebug("Status is Passed");
8343        return LESSON_STATUS_PASSED;
8344    }
8345    else if (strFirstCharOfAICCStatus == "f"){
8346        WriteToDebug("Status is Failed");
8347        return LESSON_STATUS_FAILED;
8348    }
8349    else if (strFirstCharOfAICCStatus == "c"){
8350        WriteToDebug("Status is Completed");
8351        return LESSON_STATUS_COMPLETED;
8352    }
8353    else if (strFirstCharOfAICCStatus == "b"){
8354        WriteToDebug("Status is Browsed");
8355        return LESSON_STATUS_BROWSED;
8356    }
8357    else if (strFirstCharOfAICCStatus == "i"){
8358        WriteToDebug("Status is Incomplete");
8359        return LESSON_STATUS_INCOMPLETE;
8360    }
8361    else if (strFirstCharOfAICCStatus == "n"){
8362        WriteToDebug("Status is Not Attempted");
8363        return LESSON_STATUS_NOT_ATTEMPTED;
8364    }
8365    else{
8366        WriteToDebug("ERROR - status not found");
8367        AICC_SetError(SCORM_ERROR_INVALID_STATUS, "Invalid status");
8368        return LESSON_STATUS_NOT_ATTEMPTED;
8369    }
8370}
8371
8372
8373function AICC_TranslateAudio(strAudio){
8374
8375    WriteToDebug("In AICC_TranslateAudio, strAudio=" + strAudio);
8376
8377    var intTempPreference = parseInt(strAudio, 10);
8378
8379    WriteToDebug("intTempPreference=" + intTempPreference);
8380
8381    if (intTempPreference > 0 && intTempPreference <= 100){
8382        WriteToDebug("Returning On");
8383        AICC_AudioPlayPreference = PREFERENCE_ON;
8384        AICC_intAudioVolume = intTempPreference;
8385    }
8386    else if (intTempPreference == 0){
8387        WriteToDebug("Returning Default");
8388        AICC_AudioPlayPreference = PREFERENCE_DEFAULT;
8389    }
8390    else if (intTempPreference < 0) {
8391        WriteToDebug("returning Off");
8392        AICC_AudioPlayPreference = PREFERENCE_OFF;
8393    }
8394    else{
8395        //preference is not a number
8396        WriteToDebug("Error: Invalid preference");
8397        AICC_SetError(AICC_ERROR_INVALID_PREFERENCE, "Invalid audio preference received from LMS");
8398    }
8399}
8400
8401
8402function AICC_TranslateSpeed(intAICCSpeed){
8403
8404    WriteToDebug("In AICC_TranslateSpeed, intAICCSpeed=" + intAICCSpeed);
8405
8406    var intPercentOfMax;
8407
8408    if ( ! ValidInteger(intAICCSpeed) ){
8409        WriteToDebug("ERROR - invalid integer");
8410        AICC_SetError(AICC_ERROR_INVALID_SPEED, "Invalid speed preference received from LMS - not an integer");
8411        return;
8412    }
8413
8414    intAICCSpeed = parseInt(intAICCSpeed, 10)
8415
8416    if (intAICCSpeed < -100 || intAICCSpeed > 100){
8417        WriteToDebug("ERROR - out of range");
8418        AICC_SetError(AICC_ERROR_INVALID_SPEED, "Invalid speed preference received from LMS - out of range");
8419        return;
8420
8421    }
8422
8423    AICC_Speed = intAICCSpeed;
8424
8425    intPercentOfMax = (intAICCSpeed + 100) / 2;
8426    intPercentOfMax = parseInt(intPercentOfMax, 10);
8427
8428    WriteToDebug("Returning " + intPercentOfMax);
8429
8430    AICC_intPercentOfMaxSpeed = intPercentOfMax;
8431
8432}
8433
8434
8435
8436function AICC_FormatObjectives(strObjectivesFromLMS){
8437
8438    WriteToDebug("In AICC_FormatObjectives, strObjectivesFromLMS=" + strObjectivesFromLMS);
8439
8440    var aryLines;
8441    var i;
8442    var strLineName;
8443    var strLineValue;
8444    var strLineType;
8445    var strIndex;
8446
8447    aryLines = strObjectivesFromLMS.split("\n");
8448
8449    //establish the read array with empty elements for all the IDs we'll encounter
8450    for (i=0; i < aryLines.length; i++){
8451        WriteToDebug("Extracting Index From Line: " + aryLines[i]);
8452
8453        strLineName  = GetNameFromAICCLine(aryLines[i]);
8454        strIndex = GetIndexFromAICCName(strLineName);
8455
8456        strIndex = parseInt(strIndex, 10);
8457
8458        WriteToDebug("strIndex: " + strIndex);
8459
8460        AICC_aryObjectivesRead[parseInt(strIndex, 10)] = new Array(3);
8461    }
8462
8463
8464    //loop through the line again and now populate the actual data
8465    for (i=0; i < aryLines.length; i++){
8466        WriteToDebug("Populating Line " + aryLines[i]);
8467
8468        strLineName  = GetNameFromAICCLine(aryLines[i]);
8469        strLineValue = GetValueFromAICCLine(aryLines[i]);
8470
8471        strIndex = GetIndexFromAICCName(strLineName);
8472
8473        strIndex = strIndex;
8474
8475        WriteToDebug("strLineName: " + strLineName);
8476        WriteToDebug("strLineValue: " + strLineValue);
8477        WriteToDebug("strIndex: " + strIndex);
8478
8479        strLineType = strLineName.substr(0,4).toLowerCase();
8480
8481        if (strLineType == "j_id"){
8482            WriteToDebug("Found ID");
8483            AICC_aryObjectivesRead[parseInt(strIndex, 10)][AICC_OBJ_ARRAY_ID] = strLineValue;
8484        }
8485        else if (strLineType =="j_st"){
8486            WriteToDebug("Found Status");
8487            AICC_aryObjectivesRead[parseInt(strIndex, 10)][AICC_OBJ_ARRAY_STATUS] = AICC_ConvertAICCStatusIntoLocalStatus(strLineValue.charAt(0).toLowerCase());
8488        }
8489        else if (strLineType == "j_sc"){
8490            WriteToDebug("Found Score");
8491            AICC_aryObjectivesRead[parseInt(strIndex, 10)][AICC_OBJ_ARRAY_SCORE
8491] = AICC_ExtractSingleScoreFromObjective(strLineValue);
8492        }
8493        else{
8494            WriteToDebug("WARNING - unidentified objective data found - " + aryLines[i]);
8495        }
8496
8497    }
8498
8499}
8500
8501
8502function AICC_ExtractSingleScoreFromObjective(strLineValue){
8503    WriteToDebug("In AICC_ExtractSingleScoreFromObjective, strLineValue=" + strLineValue);
8504
8505    //AICC objectives can have multiple score sets (raw,max,min) seperated by semi-colons,they represent the various attempts at an objective
8506    //we're just concerned with the most recent attempt
8507
8508    var aryParts;
8509
8510    aryParts = strLineValue.split(";");     //drop the previous instances if any
8511    aryParts = aryParts[0].split(",");      //drop the max and min scores if any
8512
8513
8514    WriteToDebug("returning " + aryParts[0]);
8515
8516    return aryParts[0];
8517
8518}
8519
8520function FindObjectiveById(strID, aryObjectives){
8521
8522    WriteToDebug("In FindObjectiveById, strID=" + strID);
8523
8524    for (var i=0; i <= aryObjectives.length; i++){
8525
8526        WriteToDebug("Searching element " + i);
8527
8528        if (aryObjectives[i]){
8529            WriteToDebug("Element Exists");
8530
8531            if (aryObjectives[i][AICC_OBJ_ARRAY_ID].toString() == strID.toString()){
8532                WriteToDebug("Element matches");
8533                return i;
8534            }
8535        }
8536    }
8537
8538    return null;
8539}
8540
8541function AICC_CreateValidIdentifier(str) {
8542    return CreateValidIdentifierLegacy(str);
8543}
8544
8545//___________________________________________________________
8546//Interaction Retrieval Functionality
8547//NOTE ON INTERACTION RETRIEVAL
8548//A.  It is only available in certain standards, standards where it is unavailable will return nothing
8549//B.  The interaction records are currently reported using "journaling", whereby each entry is appended
8550//      Retrieval methods will retrieve only the most recent value
8551
8552
8553//___________________________________________________________
8554//Helper Methods
8555function AICC_FindInteractionIndexFromID(strInteractionID){
8556
8557    WriteToDebug("AICC_FindInteractionIndexFromID - AICC does not support interaction retrieval, returning null");
8558
8559    return null;
8560}
8561//___________________________________________________________
8562
8563
8564function AICC_GetInteractionType(strInteractionID)
8565{
8566
8567    WriteToDebug("AICC_GetInteractionType - AICC does not support interaction retrieval, returning empty string");
8568    return '';
8569
8570
8571}
8572
8573//public
8574function AICC_GetInteractionTimestamp(strInteractionID)
8575{
8576    WriteToDebug("AICC_GetInteractionTimestamp - AICC does not support interaction retrieval, returning empty string");
8577    return '';
8578}
8579
8580
8581
8582//public
8583function AICC_GetInteractionCorrectResponses(strInteractionID)
8584{
8585
8586    WriteToDebug("AICC_GetInteractionCorrectResponses - AICC does not support interaction retrieval, returning empty array");
8587    return new Array();
8588
8589
8590}
8591
8592
8593
8594//public
8595function AICC_GetInteractionWeighting(strInteractionID)
8596{
8597    WriteToDebug("AICC_GetInteractionWeighting - AICC does not support interaction retrieval, returning empty string");
8598    return '';
8599
8600}
8601
8602
8603
8604//public
8605function AICC_GetInteractionLearnerResponses(strInteractionID)
8606{
8607    WriteToDebug("AICC_GetInteractionLearnerResponses - AICC does not support interaction retrieval, returning empty array");
8608    return new Array();
8609
8610
8611}
8612
8613
8614//public
8615function AICC_GetInteractionResult(strInteractionID)
8616{
8617    WriteToDebug("AICC_GetInteractionResult - AICC does not support interaction retrieval, returning empty string");
8618    return '';
8619
8620}
8621
8622
8623
8624//public
8625function AICC_GetInteractionLatency(strInteractionID)
8626{
8627    WriteToDebug("AICC_GetInteractionDescription - AICC does not support interaction retrieval, returning empty string");
8628    return '';
8629
8630}
8631
8632
8633
8634//public
8635function AICC_GetInteractionDescription(strInteractionID)
8636{
8637    WriteToDebug("AICC_GetInteractionDescription - AICC does not support interaction retrieval, returning empty string");
8638    return '';
8639}
8640
8641//________________________________________________
8642
8643//public
8644function AICC_CreateDataBucket(strBucketId, intMinSize, intMaxSize){
8645    WriteToDebug("AICC_CreateDataBucket - AICC does not support SSP, returning false");
8646    return false;
8647}
8648
8649//public
8650function AICC_GetDataFromBucket(strBucketId){
8651    WriteToDebug("AICC_GetDataFromBucket - AICC does not support SSP, returning empty string");
8652    return "";
8653}
8654
8655//public
8656function AICC_PutDataInBucket(strBucketId, strData, blnAppendToEnd){
8657    WriteToDebug("AICC_PutDataInBucket - AICC does not support SSP, returning false");
8658    return false;
8659}
8660
8661//public
8662function AICC_DetectSSPSupport(){
8663    WriteToDebug("AICC_DetectSSPSupport - AICC does not support SSP, returning false");
8664    return false;
8665}
8666
8667//public
8668function AICC_GetBucketInfo(strBucketId){
8669    WriteToDebug("AICC_DetectSSPSupport - AICC does not support SSP, returning empty SSPBucketSize");
8670    return new SSPBucketSize(0, 0);
8671}
8672
8673
8674function AICC_SetNavigationRequest(strNavRequest){
8675    WriteToDebug("AICC_SetNavigationRequest - AICC does not support navigation requests, returning false");
8676    return false;
8677}
8678
8679function AICC_GetNavigationRequest(){
8680    WriteToDebug("AICC_SetNavigationRequest - AICC does not support navigation requests, returning false");
8681    return false;
8682}
8683
8684//==================================================================
8685//==================================================================
8686
8687function FormAICCPostData(){
8688    WriteToDebug("In FormAICCPostData");
8689
8690    var strAICCData = "";
8691
8692
8693    strAICCData += "[Core]\r\n";
8694
8695    strAICCData += "Lesson_Location=" + AICC_Lesson_Location + "\r\n";
8696    strAICCData += "Lesson_Status=" + AICC_TranslateLessonStatusToAICC(AICC_Status) + "\r\n";
8697    strAICCData += "Score=" + AICC_TranslateScoreToAICC() + "\r\n";
8698    strAICCData += "Time=" + AICC_TranslateTimeToAICC() + "\r\n";
8699
8700    strAICCData += "[Comments]\r\n" + AICC_TranslateCommentsToAICC() + "\r\n";
8701
8702    strAICCData += "[Objectives_Status]\r\n" + AICC_TranslateObjectivesToAICC() + "\r\n";
8703
8704    strAICCData += "[Student_Preferences]\r\n";
8705
8706    strAICCData += "Audio=" + AICC_TranslateAudioToAICC() + "\r\n";
8707    strAICCData += "Language=" + AICC_Language + "\r\n";
8708    strAICCData += "Speed=" + AICC_TranslateSpeedToAICC() + "\r\n";
8709    strAICCData += "Text=" + AICC_TranslateTextToAICC() + "\r\n";
8710
8711    strAICCData += "[Core_Lesson]\r\n";
8712    strAICCData += AICC_Data_Chunk;
8713
8714    WriteToDebug("FormAICCPostData returning: " + strAICCData);
8715
8716    return strAICCData;
8717}
8718
8719function AICC_TranslateLessonStatusToAICC(intStatus){
8720
8721    WriteToDebug("In AICC_TranslateLessonStatusToAICC");
8722
8723    switch (intStatus){
8724
8725        case LESSON_STATUS_PASSED:
8726            WriteToDebug("Status is passed");
8727            AICC_Lesson_Status = "P";
8728        break;
8729
8730        case LESSON_STATUS_COMPLETED:
8731            WriteToDebug("Status is completed");
8732            AICC_Lesson_Status = "C";
8733        break;
8734
8735        case LESSON_STATUS_FAILED:
8736            WriteToDebug("Status is failed");
8737            AICC_Lesson_Status = "F";
8738        break;
8739
8740        case LESSON_STATUS_INCOMPLETE:
8741            WriteToDebug("Status is incomplete");
8742            AICC_Lesson_Status = "I";
8743        break;
8744
8745        case LESSON_STATUS_BROWSED:
8746            WriteToDebug("Status is browsed");
8747            AICC_Lesson_Status = "B";
8748        break;
8749
8750        case LESSON_STATUS_NOT_ATTEMPTED:
8751            WriteToDebug("Status is not attempted");
8752            AICC_Lesson_Status = "N";
8753        break;
8754    }
8755
8756    return AICC_Lesson_Status;
8757}
8758
8759function AICC_TranslateScoreToAICC(){
8760
8761    WriteToDebug("In AICC_TranslateScoreToAICC");
8762
8763    AICC_Score = AICC_fltScoreRaw;
8764
8765    //prior to version 3, AICC scores cannot contain a decimal
8766    if (AICC_LMS_Version < 3 && AICC_fltScoreRaw != ""){
8767        AICC_Score = parseInt(AICC_Score, 10);
8768    }
8769
8770    //some older versions may not have this setting available
8771    if (    (AICC_REPORT_MIN_MAX_SCORE === undefined ||
8772             AICC_REPORT_MIN_MAX_SCORE === null ||
8773             AICC_REPORT_MIN_MAX_SCORE === true) &&
8774            (AICC_LMS_Version >= 3 )        //min and max scores are only allowed after version 3
8775        ){
8776
8777        WriteToDebug("Using max and min values if available.");
8778
8779        if ((AICC_fltScoreMax != "") || (AICC_fltScoreMin != "")) {
8780            WriteToDebug("Appending Max and Min scores");
8781            AICC_Score += "," + AICC_fltScoreMax + "," + AICC_fltScoreMin;
8782        }
8783
8784    }
8785
8786    WriteToDebug("AICC_Score=" + AICC_Score);
8787
8788    return AICC_Score;
8789}
8790
8791function AICC_TranslateTimeToAICC(){
8792
8793    WriteToDebug("In AICC_TranslateTimeToAICC");
8794
8795    var strTime;
8796
8797    strTime = ConvertMilliSecondsToSCORMTime(AICC_intSessionTimeMilliseconds, false);
8798
8799    return strTime;
8800}
8801
8802function AICC_TranslateCommentsToAICC(){
8803
8804    WriteToDebug("In AICC_TranslateCommentsToAICC");
8805
8806    var strComments = "";
8807    for (var i=0; i < AICC_aryCommentsFromLearner.length; i++){
8808        strComments += "<" + (i+1) + ">" + AICC_aryCommentsFromLearner[i] + "<e." + (i+1) + ">";
8809    }
8810    return strComments;
8811}
8812
8813function AICC_TranslateObjectivesToAICC(){
8814
8815    WriteToDebug("In AICC_TranslateObjectivesToAICC");
8816
8817    var strObjectives = "";
8818
8819    for (var i=0; i<AICC_aryObjectivesWrite.length; i++){
8820
8821        WriteToDebug("Looking at index: " + i);
8822
8823        if (AICC_aryObjectivesWrite[i]){
8824            WriteToDebug("Element " + i + " exists, id=" + AICC_aryObjectivesWrite[i][AICC_OBJ_ARRAY_ID] + ", score=" + AICC_aryObjectivesWrite[i][AICC_OBJ_ARRAY_SCORE
8824] + ", status=" + AICC_aryObjectivesWrite[i][AICC_OBJ_ARRAY_STATUS]);
8825
8826            strObjectives += "J_ID." + (i+1) + "=" + AICC_aryObjectivesWrite[i][AICC_OBJ_ARRAY_ID] + "\r\n";
8827
8828            if (AICC_aryObjectivesWrite[i][AICC_OBJ_ARRAY_SCORE] != ""){
8829                strObjectives += "J_Score." + (i+1) + "=" + AICC_aryObjectivesWrite[i][AICC_OBJ_ARRAY_SCORE] + "\r\n";
8830            }
8831
8832            if (AICC_aryObjectivesWrite[i][AICC_OBJ_ARRAY_STATUS] != ""){
8833                strObjectives += "J_Status." + (i+1) + "=" + AICC_TranslateLessonStatusToAICC(AICC_aryObjectivesWrite[i][AICC_OBJ_ARRAY_STATUS]) + "\r\n";
8834            }
8835        }
8836    }
8837
8838    return strObjectives;
8839}
8840
8841function AICC_TranslateAudioToAICC(){
8842
8843    WriteToDebug("In AICC_TranslateAudioToAICC");
8844
8845    var strReturn;
8846
8847    switch (AICC_AudioPlayPreference){
8848
8849        case PREFERENCE_ON:
8850            WriteToDebug("Preference is ON");
8851            strReturn = AICC_intAudioVolume;
8852        break;
8853
8854        case PREFERENCE_DEFAULT:
8855            WriteToDebug("Preference is DEFAULT");
8856            strReturn = 0;
8857        break;
8858
8859        case PREFERENCE_OFF:
8860            WriteToDebug("Preference is OFF");
8861            strReturn = -1;
8862        break;
8863    }
8864
8865    return strReturn;
8866}
8867
8868
8869
8870function AICC_TranslateSpeedToAICC(){
8871    WriteToDebug("In AICC_TranslateSpeedToAICC");
8872
8873    var intAICCSpeed;
8874
8875    intAICCSpeed = (AICC_intPercentOfMaxSpeed * 2) - 100;
8876
8877    return intAICCSpeed;
8878}
8879
8880function AICC_TranslateTextToAICC(){
8881    WriteToDebug("In AICC_TranslateTextToAICC");
8882
8883    var strPreference = 0;
8884
8885    if (AICC_TextPreference == PREFERENCE_OFF){
8886        strPreference = -1;
8887    }
8888    else if (AICC_TextPreference == PREFERENCE_DEFAULT){
8889        strPreference = 0;
8890    }
8891    else if (AICC_TextPreference == PREFERENCE_ON){
8892        strPreference = 1;
8893    }
8894
8895    return strPreference;
8896}
8897
8898
8899
8900//==================================================================
8901//interactions
8902//==================================================================
8903
8904
8905function FormAICCInteractionsData(){
8906
8907    WriteToDebug("In FormAICCInteractionsData");
8908
8909    var strInteractions;
8910    var strDate;
8911    var strTime;
8912    var strResult = "";
8913
8914    strInteractions = '"course_id","student_id","lesson_id","date","time","interaction_id",' +
8915                      '"objective_id","type_interaction","correct_response","student_response",' +
8916                      '"result","weighting","latency"\r\n';
8917
8918    var blnCorrect = "";
8919    var strResponse = "";
8920    var strCorrectResponse = "";
8921    var strLatency = "";
8922
8923    for (var i=0; i < AICC_aryInteractions.length; i++){
8924
8925        blnCorrect = AICC_aryInteractions[i][AICC_INTERACTIONS_CORRECT];
8926        strResult = "";
8927
8928        //need to leave support for blnCorrect=t/f for legacy implementations of RSECA
8929        //if it's null, leave it as "" (not every interaction is correct/incorrect
8930
8931        if (blnCorrect == true || blnCorrect == INTERACTION_RESULT_CORRECT){
8932            strResult = AICC_RESULT_CORRECT;
8933        }
8934        else if (blnCorrect == "false" || blnCorrect == INTERACTION_RESULT_WRONG){          //compare against the string "false" because ("" == false) evaluates to true
8935            strResult = AICC_RESULT_WRONG;
8936        }
8937        else if (blnCorrect == INTERACTION_RESULT_UNANTICIPATED){
8938            strResult = AICC_RESULT_UNANTICIPATED;
8939        }
8940        else if (blnCorrect == INTERACTION_RESULT_NEUTRAL){
8941            strResult = AICC_RESULT_NEUTRAL;
8942        }
8943
8944        strDate = ConvertDateToCMIDate(AICC_aryInteractions[i][AICC_INTERACTIONS_TIME_STAMP]);
8945        strTime = ConvertDateToCMITime(AICC_aryInteractions[i][AICC_INTERACTIONS_TIME_STAMP]);
8946
8947
8948        if (blnUseLongInteractionResultValues == true){
8949            strResponse = AICC_aryInteractions[i][AICC_INTERACTIONS_RESPONSE_LONG];
8950            strCorrectResponse = AICC_aryInteractions[i][AICC_INTERACTIONS_CORRECT_RESPONSE_LONG];
8951        }
8952        else{
8953            strResponse = AICC_aryInteractions[i][AICC_INTERACTIONS_RESPONSE];
8954            strCorrectResponse = AICC_aryInteractions[i][AICC_INTERACTIONS_CORRECT_RESPONSE];
8955        }
8956
8957        strResponse = new String(strResponse);
8958        strCorrectResponse = new String(strCorrectResponse);
8959
8960        var tempLatency = AICC_aryInteractions[i][AICC_INTERACTIONS_LATENCY];
8961
8962        if (tempLatency !== null && tempLatency !== undefined && tempLatency != ""){
8963            strLatency = ConvertMilliSecondsToSCORMTime(tempLatency, false);
8964        }
8965
8966        strInteractions += '"' + AICC_CourseID.replace("\"", "") + '","' + AICC_Student_ID.replace("\"", "") + '","' + AICC_LESSON_ID.replace("\"", "") + '","' +
8967                           strDate + '","' + strTime + '","' + AICC_aryInteractions[i][AICC_INTERACTIONS_ID].replace("\"", "") + '",' +
8968                           '""' + ',"' + AICC_aryInteractions[i][AICC_INTERACTIONS_TYPE] + '","' + strCorrectResponse.replace("\"", "") + '","' +
8969                           strResponse.replace("\"", "") + '","' + strResult + '","' +
8970                           AICC_aryInteractions[i][AICC_INTERACTIONS_WEIGHTING] + '","' + strLatency + '"\r\n';
8971    }
8972
8973    return strInteractions;
8974}
8975
8976
8977/*
8978moved to UtilityFunctions.js ConvertToCMIDate
8979function ConvertToCMIDate(dtmDate){
8980
8981    WriteToDebug("In ConvertToCMIDate");
8982
8983    var strYear;
8984    var strMonth;
8985    var strDay;
8986    var strReturn;
8987
8988    dtmDate = new Date(dtmDate);
8989
8990    strYear = dtmDate.getFullYear()
8991    strMonth = dtmDate.getMonth();
8992    strDay = dtmDate.getDate();
8993
8994    strReturn = ZeroPad(strYear, 4) + "/" + ZeroPad(strMonth, 2) + "/" + ZeroPad(strDay, 2);
8995
8996    return strReturn;
8997}
8998*/
8999/*
9000moved to UtilityFunctions.js ConvertDateToCMITime
9001function ConvertToCMITime(dtmDate){
9002
9003    WriteToDebug("In ConvertToCMITime");
9004
9005    var strHours;
9006    var strMinutes;
9007    var strSeconds;
9008    var strReturn;
9009
9010    dtmDate = new Date(dtmDate);
9011
9012    strHours = dtmDate.getHours();
9013    strMinutes = dtmDate.getMinutes();
9014    strSeconds = dtmDate.getSeconds();
9015
9016    strReturn = ZeroPad(strHours, 2) + ":" + ZeroPad(strMinutes) + ":" + ZeroPad(strSeconds, 2);
9017
9018    return strReturn;
9019}
9020*/
9021
9022//==================================================================
9023//debug stuff
9024//==================================================================
9025
9026//used for debug only
9027function DisplayAICCVariables(){
9028
9029    var strAlert = "";
9030
9031    strAlert += "AICC_Student_ID = " + AICC_Student_ID + "\n";
9032    strAlert += "AICC_Student_Name = " + AICC_Student_Name + "\n";
9033    strAlert += "AICC_Lesson_Location = " + AICC_Lesson_Location + "\n";
9034    strAlert += "AICC_Score = " + AICC_Score + "\n";
9035    strAlert += "AICC_Credit = " + AICC_Credit + "\n";
9036    strAlert += "AICC_Lesson_Status = " + AICC_Lesson_Status + "\n";
9037    strAlert += "AICC_Time = " + AICC_Time + "\n";
9038    strAlert += "AICC_Mastery_Score = " + AICC_Mastery_Score + "\n";
9039    strAlert += "AICC_Lesson_Mode = " + AICC_Lesson_Mode + "\n";
9040    strAlert += "AICC_Max_Time_Allowed = " + AICC_Max_Time_Allowed + "\n";
9041    strAlert += "AICC_Time_Limit_Action = " + AICC_Time_Limit_Action + "\n";
9042    strAlert += "AICC_Audio = " + AICC_Audio + "\n";
9043    strAlert += "AICC_Speed = " + AICC_Speed + "\n";
9044    strAlert += "AICC_Language = " + AICC_Language + "\n";
9045    strAlert += "AICC_Text = " + AICC_Text + "\n";
9046    strAlert += "AICC_Launch_Data = " + AICC_Launch_Data + "\n";
9047    strAlert += "AICC_Data_Chunk = " + AICC_Data_Chunk + "\n";
9048    strAlert += "AICC_Comments = " + AICC_Comments + "\n";
9049    strAlert += "AICC_Objectives = " + AICC_Objectives + "\n";
9050
9051    alert(strAlert)
9052}
9053
9054
9055
9056
9057function NONE_Initialize(){
9058    WriteToDebug("In NONE_Initialize, Returning true");
9059
9060    InitializeExecuted(true, "");
9061
9062    return true;
9063}
9064
9065
9066function NONE_Finish(strExitType, blnStatusWasSet){
9067    WriteToDebug("In NONE_Finish, Returning true");
9068    return true;
9069}
9070
9071
9072function NONE_CommitData(){
9073    WriteToDebug("In NONE_CommitData, Returning true");
9074    return true;
9075}
9076
9077function NONE_GetStudentID(){
9078    WriteToDebug("In NONE_GetStudentID, Returning ''");
9079    return "";
9080}
9081
9082function NONE_GetStudentName(){
9083    WriteToDebug("In NONE_GetStudentName, Returning ''");
9084    return "";
9085}
9086
9087function NONE_GetBookmark(){
9088    WriteToDebug("In NONE_GetBookmark, Returning ''");
9089    return "";
9090}
9091
9092function NONE_SetBookmark(strBookmark){
9093    WriteToDebug("In NONE_SetBookmark, Returning true");
9094    return true;
9095}
9096
9097function NONE_GetDataChunk(){
9098    WriteToDebug("In NONE_GetDataChunk, Returning ''");
9099    return "";
9100}
9101
9102function NONE_SetDataChunk(strData){
9103    WriteToDebug("In NONE_SetDataChunk, Returning true");
9104    return true;
9105}
9106
9107
9108function NONE_GetLaunchData(){
9109    WriteToDebug("In NONE_GetLaunchData, Returning ''");
9110    return "";
9111}
9112
9113function NONE_GetComments(){
9114    WriteToDebug("In NONE_GetComments, Returning ''");
9115    return "";
9116}
9117
9118function NONE_WriteComment(strComment){
9119    WriteToDebug("In NONE_WriteComment, Returning true");
9120    return true;
9121}
9122
9123function NONE_GetLMSComments(){
9124    WriteToDebug("In NONE_GetLMSComments, Returning ''");
9125    return "";
9126}
9127
9128
9129function NONE_GetAudioPlayPreference(){
9130    WriteToDebug("In NONE_GetAudioPlayPreference, Returning " + PREFERENCE_DEFAULT);
9131    return PREFERENCE_DEFAULT;
9132}
9133
9134
9135
9136function NONE_GetAudioVolumePreference(){
9137    WriteToDebug("In NONE_GetAudioVolumePreference, Returning 100");
9138    return 100;
9139}
9140
9141
9142function NONE_SetAudioPreference(PlayPreference, intPercentOfMaxSpeed){
9143    WriteToDebug("In NONE_SetAudioPreference, Returning true");
9144    return true;
9145}
9146
9147
9148
9149function NONE_SetLanguagePreference(strLanguage){
9150    WriteToDebug("In NONE_SetLanguagePreference, Returning true");
9151    return true;
9152}
9153
9154
9155function NONE_GetLanguagePreference(){
9156    WriteToDebug("In NONE_GetLanguagePreference, Returning ''");
9157    return "";
9158}
9159
9160
9161function NONE_SetSpeedPreference(intPercentOfMax){
9162    WriteToDebug("In NONE_SetSpeedPreference, Returning true");
9163    return true;
9164}
9165
9166
9167function NONE_GetSpeedPreference(){
9168    WriteToDebug("In NONE_GetSpeedPreference, Returning 100");
9169    return 100;
9170}
9171
9172function NONE_SetTextPreference(intPreference){
9173    WriteToDebug("In NONE_SetTextPreference, Returning true");
9174    return true;
9175}
9176
9177
9178function NONE_GetTextPreference(){
9179    WriteToDebug("In NONE_GetTextPreference, Returning " + PREFERENCE_DEFAULT);
9180    return PREFERENCE_DEFAULT;
9181}
9182
9183function NONE_GetPreviouslyAccumulatedTime(){
9184    WriteToDebug("In NONE_GetPreviouslyAccumulatedTime, Returning 0");
9185    return 0;
9186}
9187
9188
9189function NONE_SaveTime(intMilliSeconds){
9190    WriteToDebug("In intMilliSeconds, Returning true");
9191    return true;
9192}
9193
9194function NONE_GetMaxTimeAllowed(){
9195    WriteToDebug("In NONE_GetMaxTimeAllowed, Returning 36002439999");
9196    return MAX_CMI_TIME;
9197}
9198
9199
9200
9201function NONE_DisplayMessageOnTimeout(){
9202    WriteToDebug("In NONE_DisplayMessageOnTimeout, Returning false");
9203    return false;
9204}
9205
9206function NONE_ExitOnTimeout(){
9207    WriteToDebug("In NONE_ExitOnTimeout, Returning false");
9208    return false;
9209}
9210
9211
9212function NONE_GetPassingScore(){
9213    WriteToDebug("In NONE_GetPassingScore, Returning ''");
9214    return '';
9215}
9216
9217
9218
9219function NONE_GetScore(){
9220    WriteToDebug("In NONE_GetScore, Returning 0");
9221    return 0;
9222}
9223
9224function NONE_SetScore(intScore, intMaxScore, intMinScore){
9225    WriteToDebug("In NONE_SetScore, Returning true");
9226    return true;
9227}
9228
9229
9230
9231function NONE_RecordTrueFalseInteraction(){
9232    WriteToDebug("In NONE_RecordTrueFalseInteraction, Returning true");
9233    return true;
9234}
9235function NONE_RecordMultipleChoiceInteraction(strID, strResponse, blnCorrect, strCorrectResponse){
9236    WriteToDebug("In NONE_RecordMultipleChoiceInteraction, Returning true");
9237    return true;
9238}
9239function NONE_RecordFillInInteraction(){
9240    WriteToDebug("In NONE_RecordFillInInteraction, Returning true");
9241    return true;
9242}
9243function NONE_RecordMatchingInteraction(){
9244    WriteToDebug("In NONE_RecordMatchingInteraction, Returning true");
9245    return true;
9246}
9247function NONE_RecordPerformanceInteraction(){
9248    WriteToDebug("In NONE_RecordPerformanceInteraction, Returning true");
9249    return true;
9250}
9251function NONE_RecordSequencingInteraction(){
9252    WriteToDebug("In NONE_RecordSequencingInteraction, Returning true");
9253    return true;
9254}
9255function NONE_RecordLikertInteraction(){
9256    WriteToDebug("In RecordLikertInteraction, Returning true");
9257    return true;
9258}
9259function NONE_RecordNumericInteraction(){
9260    WriteToDebug("In NONE_RecordNumericInteraction, Returning true");
9261    return true;
9262}
9263
9264
9265function NONE_GetEntryMode(){
9266    WriteToDebug("In NONE_GetEntryMode, Returning " + ENTRY_FIRST_TIME);
9267    return ENTRY_FIRST_TIME;
9268}
9269
9270function NONE_GetLessonMode(){
9271    WriteToDebug("In NONE_GetLessonMode, Returning " + MODE_NORMAL);
9272    return MODE_NORMAL;
9273}
9274
9275function NONE_GetTakingForCredit(){
9276    WriteToDebug("In NONE_GetTakingForCredit, Returning true");
9277    return true;
9278}
9279
9280
9281
9282function NONE_SetObjectiveScore(strObjectiveID, intScore, intMaxScore, intMinScore){
9283    WriteToDebug("In NONE_SetObjectiveScore, Returning true");
9284    return true;
9285}
9286
9287
9288function NONE_SetObjectiveStatus(strObjectiveID, Lesson_Status){
9289    WriteToDebug("In NONE_SetObjectiveStatus, Returning true");
9290    return true;
9291}
9292
9293function NONE_SetObjectiveDescription(strObjectiveID, strObjectiveDescription){
9294    WriteToDebug("In NONE_SetObjectiveDescription, Returning true");
9295    return true;
9296}
9297
9298
9299function NONE_GetObjectiveScore(strObjectiveID){
9300    WriteToDebug("In NONE_SetObjectiveScore, Returning ''");
9301    return '';
9302}
9303
9304
9305function NONE_GetObjectiveStatus(strObjectiveID){
9306    WriteToDebug("In NONE_SetObjectiveStatus, Returning Not Attempted");
9307    return LESSON_STATUS_NOT_ATTEMPTED;
9308}
9309
9310function NONE_GetObjectiveDescription(strObjectiveID){
9311    WriteToDebug("In NONE_GetObjectiveDescription, ''");
9312    return "";
9313}
9314
9315function NONE_CreateValidIdentifier(str) {
9316    return CreateUriIdentifier(str);
9317}
9318
9319//___________________________________________________________
9320//Interaction Retrieval Functionality
9321//NOTE ON INTERACTION RETRIEVAL
9322//A.  It is only available in certain standards, standards where it is unavailable will return nothing
9323//B.  The interaction records are currently reported using "journaling", whereby each entry is appended
9324//      Retrieval methods will retrieve only the most recent value
9325
9326
9327//___________________________________________________________
9328//Helper Methods
9329function NONE_FindInteractionIndexFromID(strInteractionID){
9330
9331    WriteToDebug("NONE_FindInteractionIndexFromID - NONE does not support interaction retrieval, returning null");
9332
9333    return null;
9334}
9335//___________________________________________________________
9336
9337
9338function NONE_GetInteractionType(strInteractionID)
9339{
9340
9341    WriteToDebug("NONE_GetInteractionType - NONE does not support interaction retrieval, returning empty string");
9342    return '';
9343
9344
9345}
9346
9347//public
9348function NONE_GetInteractionTimestamp(strInteractionID)
9349{
9350    WriteToDebug("NONE_GetInteractionTimestamp - NONE does not support interaction retrieval, returning empty string");
9351    return '';
9352}
9353
9354
9355
9356//public
9357function NONE_GetInteractionCorrectResponses(strInteractionID)
9358{
9359
9360    WriteToDebug("NONE_GetInteractionCorrectResponses - NONE does not support interaction retrieval, returning empty array");
9361    return new Array();
9362
9363
9364}
9365
9366
9367
9368//public
9369function NONE_GetInteractionWeighting(strInteractionID)
9370{
9371    WriteToDebug("NONE_GetInteractionWeighting - NONE does not support interaction retrieval, returning empty string");
9372    return '';
9373
9374}
9375
9376
9377
9378//public
9379function NONE_GetInteractionLearnerResponses(strInteractionID)
9380{
9381    WriteToDebug("NONE_GetInteractionLearnerResponses - NONE does not support interaction retrieval, returning empty array");
9382    return new Array();
9383
9384
9385}
9386
9387
9388//public
9389function NONE_GetInteractionResult(strInteractionID)
9390{
9391    WriteToDebug("NONE_GetInteractionResult - NONE does not support interaction retrieval, returning empty string");
9392    return '';
9393
9394}
9395
9396
9397
9398//public
9399function NONE_GetInteractionLatency(strInteractionID)
9400{
9401    WriteToDebug("NONE_GetInteractionDescription - NONE does not support interaction retrieval, returning empty string");
9402    return '';
9403
9404}
9405
9406
9407
9408//public
9409function NONE_GetInteractionDescription(strInteractionID)
9410{
9411    WriteToDebug("NONE_GetInteractionDescription - NONE does not support interaction retrieval, returning empty string");
9412    return '';
9413
9414
9415}
9416
9417//public
9418function NONE_CreateDataBucket(strBucketId, intMinSize, intMaxSize){
9419    WriteToDebug("NONE_CreateDataBucket - NONE does not support SSP, returning false");
9420    return false;
vendor: 4,960 bytes, lines 9421-9538
9421}
9422
9423//public
9424function NONE_GetDataFromBucket(strBucketId){
9425    WriteToDebug("NONE_GetDataFromBucket - NONE does not support SSP, returning empty string");
9426    return "";
9427}
9428
9429//public
9430function NONE_PutDataInBucket(strBucketId, strData, blnAppendToEnd){
9431    WriteToDebug("NONE_PutDataInBucket - NONE does not support SSP, returning false");
9432    return false;
9433}
9434
9435//public
9436function NONE_DetectSSPSupport(){
9437    WriteToDebug("NONE_DetectSSPSupport - NONE does not support SSP, returning false");
9438    return false;
9439}
9440
9441//public
9442function NONE_GetBucketInfo(strBucketId){
9443    WriteToDebug("NONE_DetectSSPSupport - NONE does not support SSP, returning empty SSPBucketSize");
9444    return new SSPBucketSize(0, 0);
9445}
9446
9447
9448//________________________________________________
9449
9450function NONE_SetNavigationRequest(strNavRequest){
9451    WriteToDebug("NONE_SetNavigationRequest - NONE does not support navigation requests, returning false");
9452    return false;
9453}
9454
9455function NONE_GetNavigationRequest(){
9456    WriteToDebug("NONE_GetNavigationRequest - NONE does not support navigation requests, returning false");
9457    return false;
9458}
9459
9460
9461function NONE_SetFailed(){
9462    WriteToDebug("In NONE_SetFailed, Returning true");
9463    return true;
9464}
9465
9466function NONE_SetPassed(){
9467    WriteToDebug("In NONE_SetPassed, Returning true");
9468    return true;
9469}
9470
9471function NONE_SetCompleted(){
9472    WriteToDebug("In NONE_SetCompleted, Returning true");
9473    return true;
9474}
9475
9476function NONE_ResetStatus(){
9477    WriteToDebug("In NONE_ResetStatus, Returning true");
9478    return true;
9479}
9480
9481function NONE_GetStatus(){
9482    WriteToDebug("In NONE_GetStatus, Returning " + LESSON_STATUS_INCOMPLETE);
9483    return LESSON_STATUS_INCOMPLETE;
9484}
9485
9486//public
9487function NONE_GetProgressMeasure(){
9488    WriteToDebug("NONE_GetProgressMeasure - NONE does not support progress_measure, returning false");
9489    return false;
9490}
9491//public
9492function NONE_SetProgressMeasure(){
9493    WriteToDebug("NONE_SetProgressMeasure - NONE does not support progress_measure, returning false");
9494    return false;
9495}
9496
9497//public
9498function NONE_GetObjectiveProgressMeasure(){
9499    WriteToDebug("NONE_GetObjectiveProgressMeasure - NONE does not support progress_measure, returning false");
9500    return false;
9501}
9502//public
9503function NONE_SetObjectiveProgressMeasure(){
9504    WriteToDebug("NONE_SetObjectiveProgressMeasure - NONE does not support progress_measure, returning false");
9505    return false;
9506}
9507
9508function NONE_SetPointBasedScore(intScore, intMaxScore, intMinScore){
9509    WriteToDebug("NONE_SetPointBasedScore - NONE does not support SetPointBasedScore, returning false");
9510    return false;
9511}
9512
9513function NONE_GetScaledScore(intScore, intMaxScore, intMinScore){
9514    WriteToDebug("NONE_GetScaledScore - NONE does not support GetScaledScore, returning false");
9515    return false;
9516}
9517
9518function NONE_GetLastError(){
9519    WriteToDebug("In NONE_GetLastError, Returning " + NO_ERROR);
9520    return NO_ERROR;
9521}
9522
9523function NONE_GetLastErrorDesc(){
9524    WriteToDebug("In NONE_GetLastErrorDesc, Returning ''");
9525    return "";
9526}
9527
9528"0.50.0";
9529/*
9530CryptoJS v3.1.2
9531code.google.com/p/crypto-js
9532(c) 2009-2013 by Jeff Mott. All rights reserved.
9533code.google.com/p/crypto-js/wiki/License
9534*/
9535var CryptoJS=CryptoJS||function(e,m){var p={},j=p.lib={},l=function(){},f=j.Base={extend:function(a){l.prototype=this;var c=new l;a&&c.mixIn(a);c.hasOwnProperty("init")||(c.init=function(){c.$super.init.apply(this,arguments)});c.init.prototype=c;c.$super=this;return c},create:function(){var a=this.extend();a.init.apply(a,arguments);return a},init:function(){},mixIn:function(a){for(var c in a)a.hasOwnProperty(c)&&(this[c]=a[c]);a.hasOwnProperty("toString")&&(this.toString=a.toString)},clone:function(){return this.init.prototype.extend(this)}},
9536n=j.WordArray=f.extend({init:function(a,c){a=this.words=a||[];this.sigBytes=c!=m?c:4*a.length},toString:function(a){return(a||h).stringify(this)},concat:function(a){var c=this.words,q=a.words,d=this.sigBytes;a=a.sigBytes;this.clamp();if(d%4)for(var b=0;b<a;b++)c[d+b>>>2]|=(q[b>>>2]>>>24-8*(b%4)&255)<<24-8*((d+b)%4);else if(65535<q.length)for(b=0;b<a;b+=4)c[d+b>>>2]=q[b>>>2];else c.push.apply(c,q);this.sigBytes+=a;return this},clamp:function(){var a=this.words,c=this.sigBytes;a[c>>>2]&=4294967295<<
953732-8*(c%4);a.length=e.ceil(c/4)},clone:function(){var a=f.clone.call(this);a.words=this.words.slice(0);return a},random:function(a){for(var c=[],b=0;b<a;b+=4)c.push(4294967296*e.random()|0);return new n.init(c,a)}}),b=p.enc={},h=b.Hex={stringify:function(a){var c=a.words;a=a.sigBytes;for(var b=[],d=0;d<a;d++){var f=c[d>>>2]>>>24-8*(d%4)&255;b.push((f>>>4).toString(16));b.push((f&15).toString(16))}return b.join("")},parse:function(a){for(var c=a.length,b=[],d=0;d<c;d+=2)b[d>>>3]|=parseInt(a.substr(d,
95382),16)<<24-4*(d%8);return new n.init(b,c/2)}},g=b.Latin1={stringify:function(a){var c=a.words;a=a.sigBytes;for(var b=[],d=0;d<a;d++)b.push(String.fromCharCode(c[d>>>2]>>>24-8*(d%4)&255));return b.join("")},parse:function(a){for(var c=a.length,b=[],d=0;d<c;d++)b[d>>>
95382]|=(a.charCodeAt(d)&255)<<24-8*(d%4);return new n.init(b,c)}},r=b.Utf8={stringify:function(a){try{return decodeURIComponent(escape(g.stringify(a)))}catch(c){throw Error("Malformed UTF-8 data");}},parse:function(a){return g.parse(unescape(encodeURIComponent(a)))}},
9539k=j.BufferedBlockAlgorithm=f.extend({reset:function(){this._data=new n.init;this._nDataBytes=0},_append:function(a){"string"==typeof a&&(a=r.parse(a));this._data.concat(a);this._nDataBytes+=a.sigBytes},_process:function(a){var c=this._data,b=c.words,d=c.sigBytes,f=this.blockSize,h=d/(4*f),h=a?e.ceil(h):e.max((h|0)-this._minBufferSize,0);a=h*f;d=e.min(4*a,d);if(a){for(var g=0;g<a;g+=f)this._doProcessBlock(b,g);g=b.splice(0,a);c.sigBytes-=d}return new n.init(g,d)},clone:function(){var a=f.clone.call(this);
9540a._data=this._data.clone();return a},_minBufferSize:0});j.Hasher=k.extend({cfg:f.extend(),init:function(a){this.cfg=this.cfg.extend(a);this.reset()},reset:function(){k.reset.call(this);this._doReset()},update:function(a){this._append(a);this._process();return this},finalize:function(a){a&&this._append(a);return this._doFinalize()},blockSize:16,_createHelper:function(a){return function(c,b){return(new a.init(b)).finalize(c)}},_createHmacHelper:function(a){return function(b,f){return(new s.HMAC.init(a,
9541f)).finalize(b)}}});var s=p.algo={};return p}(Math);
9542(function(){var e=CryptoJS,m=e.lib,p=m.WordArray,j=m.Hasher,l=[],m=e.algo.SHA1=j.extend({_doReset:function(){this._hash=new p.init([1732584193,4023233417,2562383102,271733878,3285377520])},_doProcessBlock:function(f,n){for(var b=this._hash.words,h=b[0],g=b[1],e=b[2],k=b[3],j=b[4],a=0;80>a;a++){if(16>a)l[a]=f[n+a]|0;else{var c=l[a-3]^l[a-8]^l[a-14]^l[a-16];l[a]=c<<1|c>>>31}c=(h<<5|h>>>27)+j+l[a];c=20>
vendor: 5,250 bytes, lines 9542-9564
9542a?c+((g&e|~g&k)+1518500249):40>a?c+((g^e^k)+1859775393):60>a?c+((g&e|g&k|e&k)-1894007588):c+((g^e^
9543k)-899497514);j=k;k=e;e=g<<30|g>>>2;g=h;h=c}b[0]=b[0]+h|0;b[1]=b[1]+g|0;b[2]=b[2]+e|0;b[3]=b[3]+k|0;b[4]=b[4]+j|0},_doFinalize:function(){var f=this._data,e=f.words,b=8*this._nDataBytes,h=8*f.sigBytes;e[h>>>5]|=128<<24-h%32;e[(h+64>>>9<<4)+14]=Math.floor(b/4294967296);e[(h+64>>>9<<4)+15]=b;f.sigBytes=4*e.length;this._process();return this._hash},clone:function(){var e=j.clone.call(this);e._hash=this._hash.clone();return e}});e.SHA1=j._createHelper(m);e.HmacSHA1=j._createHmacHelper(m)})();
9544
9545/*
9546CryptoJS v3.1.2
9547code.google.com/p/crypto-js
9548(c) 2009-2013 by Jeff Mott. All rights reserved.
9549code.google.com/p/crypto-js/wiki/License
9550*/
9551var CryptoJS=CryptoJS||function(h,s){var f={},t=f.lib={},g=function(){},j=t.Base={extend:function(a){g.prototype=this;var c=new g;a&&c.mixIn(a);c.hasOwnProperty("init")||(c.init=function(){c.$super.init.apply(this,arguments)});c.init.prototype=c;c.$super=this;return c},create:function(){var a=this.extend();a.init.apply(a,arguments);return a},init:function(){},mixIn:function(a){for(var c in a)a.hasOwnProperty(c)&&(this[c]=a[c]);a.hasOwnProperty("toString")&&(this.toString=a.toString)},clone:function(){return this.init.prototype.extend(this)}},
9552q=t.WordArray=j.extend({init:function(a,c){a=this.words=a||[];this.sigBytes=c!=s?c:4*a.length},toString:function(a){return(a||u).stringify(this)},concat:function(a){var c=this.words,d=a.words,b=this.sigBytes;a=a.sigBytes;this.clamp();if(b%4)for(var e=0;e<a;e++)c[b+e>>>2]|=(d[e>>>2]>>>24-8*(e%4)&255)<<24-8*((b+e)%4);else if(65535<d.length)for(e=0;e<a;e+=4)c[b+e>>>2]=d[e>>>2];else c.push.apply(c,d);this.sigBytes+=a;return this},clamp:function(){var a=this.words,c=this.sigBytes;a[c>>>2]&=4294967295<<
955332-8*(c%4);a.length=h.ceil(c/4)},clone:function(){var a=j.clone.call(this);a.words=this.words.slice(0);return a},random:function(a){for(var c=[],d=0;d<a;d+=4)c.push(4294967296*h.random()|0);return new q.init(c,a)}}),v=f.enc={},u=v.Hex={stringify:function(a){var c=a.words;a=a.sigBytes;for(var d=[],b=0;b<a;b++){var e=c[b>>>2]>>>24-8*(b%4)&255;d.push((e>>>4).toString(16));d.push((e&15).toString(16))}return d.join("")},parse:function(a){for(var c=a.length,d=[],b=0;b<c;b+=2)d[b>>>3]|=parseInt(a.substr(b,
95542),16)<<24-4*(b%8);return new q.init(d,c/2)}},k=v.Latin1={stringify:function(a){var c=a.words;a=a.sigBytes;for(var d=[],b=0;b<a;b++)d.push(String.fromCharCode(c[b>>>2]>>>24-8*(b%4)&255));return d.join("")},parse:function(a){for(var c=a.length,d=[],b=0;b<c;b++)d[b>>>2]|=(a.charCodeAt(b)&255)<<24-8*(b%4);return new q.init(d,c)}},l=v.Utf8={stringify:function(a){try{return decodeURIComponent(escape(k.stringify(a)))}catch(c){throw Error("Malformed UTF-8 data");}},parse:function(a){return k.parse(unescape(encodeURIComponent(a)))}},
9555x=t.BufferedBlockAlgorithm=j.extend({reset:function(){this._data=new q.init;this._nDataBytes=0},_append:function(a){"string"==typeof a&&(a=l.parse(a));this._data.concat(a);this._nDataBytes+=a.sigBytes},_process:function(a){var c=this._data,d=c.words,b=c.sigBytes,e=this.blockSize,f=b/(4*e),f=a?h.ceil(f):h.max((f|0)-this._minBufferSize,0);a=f*e;b=h.min(4*a,b);if(a){for(var m=0;m<a;m+=e)this._doProcessBlock(d,m);m=d.splice(0,a);c.sigBytes-=b}return new q.init(m,b)},clone:function(){var a=j.clone.call(this);
9556a._data=this._data.clone();return a},_minBufferSize:0});t.Hasher=x.extend({cfg:j.extend(),init:function(a){this.cfg=this.cfg.extend(a);this.reset()},reset:function(){x.reset.call(this);this._doReset()},update:function(a){this._append(a);this._process();return this},finalize:function(a){a&&this._append(a);return this._doFinalize()},blockSize:16,_createHelper:function(a){return function(c,d){return(new a.init(d)).finalize(c)}},_createHmacHelper:function(a){return function(c,d){return(new w.HMAC.init(a,
9557d)).finalize(c)}}});var w=f.algo={};return f}(Math);
9558(function(h){for(var s=CryptoJS,f=s.lib,t=f.WordArray,g=f.Hasher,f=s.algo,j=[],q=[],v=function(a){return 4294967296*(a-(a|0))|0},u=2,k=0;64>k;){var l;a:{l=u;for(var x=h.sqrt(l),w=2;w<=x;w++)if(!(l%w)){l=!1;break a}l=!0}l&&(8>k&&(j[k]=v(h.pow(u,0.5))),q[k]=v(h.pow(u,1/3)),k++);u++}var a=[],f=f.SHA256=g.extend({_doReset:function(){this._hash=new t.init(j.slice(0))},_doProcessBlock:function(c,d){for(var b=this._hash.words,e=b[0],f=b[1],m=b[2],h=b[3],p=b[4],j=b[5],k=b[6],l=b[7],n=0;64>n;n++){if(16>n)a[n]=
9559c[d+n]|0;else{var r=a[n-15],g=a[n-2];a[n]=((r<<25|r>>>7)^(r<<14|r>>>18)^r>>>3)+a[n-7]+((g<<15|g>>>17)^(g<<13|g>>>19)^g>>>10)+a[n-16]}r=l+((p<<26|p>>>6)^(p<<21|p>>>11)^(p<<7|p>>>25))+(p&j^~p&k)+q[n]+a[n];g=((e<<30|e>>>2)^(e<<19|e>>>13)^(e<<10|e>>>22))+(e&f^e&m^f&m);l=k;k=j;j=p;p=h+r|0;h=m;m=f;f=e;e=r+g|0}b[0]=b[0]+e|0;b[1]=b[1]+f|0;b[2]=b[2]+m|0;b[3]=b[3]+h|0;b[4]=b[4]+p|0;b[5]=b[5]+j|0;b[6]=b[6]+k|0;b[7]=b[7]+l|0},_doFinalize:function(){var a=this._data,d=a.words,b=8*this._nDataBytes,e=8*a.sigBytes;
9560d[e>>>5]|=128<<24-e%32;d[(e+64>>>9<<4)+14]=h.floor(b/4294967296);d[(e+64>>>9<<4)+15]=b;a.sigBytes=4*d.length;this._process();return this._hash},clone:function(){var a=g.clone.call(this);a._hash=this._hash.clone();return a}});s.SHA256=g._createHelper(f);s.HmacSHA256=g._createHmacHelper(f)})(Math);
9561
9562/*
9563CryptoJS v3.1.2
9564code.google.com/p/crypto-js
vendor: 5,309 bytes, lines 9565-9738
9565(c) 2009-2013 by Jeff Mott. All rights reserved.
9566code.google.com/p/crypto-js/wiki/License
9567*/
9568(function () {
9569    // Shortcuts
9570    var C = CryptoJS;
9571    var C_lib = C.lib;
9572    var WordArray = C_lib.WordArray;
9573    var C_enc = C.enc;
9574
9575    /**
9576     * Base64 encoding strategy.
9577     */
9578    var Base64 = C_enc.Base64 = {
9579        /**
9580         * Converts a word array to a Base64 string.
9581         *
9582         * @param {WordArray} wordArray The word array.
9583         *
9584         * @return {string} The Base64 string.
9585         *
9586         * @static
9587         *
9588         * @example
9589         *
9590         *     var base64String = CryptoJS.enc.Base64.stringify(wordArray);
9591         */
9592        stringify: function (wordArray) {
9593            // Shortcuts
9594            var words = wordArray.words;
9595            var sigBytes = wordArray.sigBytes;
9596            var map = this._map;
9597
9598            // Clamp excess bits
9599            wordArray.clamp();
9600
9601            // Convert
9602            var base64Chars = [];
9603            for (var i = 0; i < sigBytes; i += 3) {
9604                var byte1 = (words[i >>> 2]       >>> (24 - (i % 4) * 8))       & 0xff;
9605                var byte2 = (words[(i + 1) >>> 2] >>> (24 - ((i + 1) % 4) * 8)) & 0xff;
9606                var byte3 = (words[(i + 2) >>> 2] >>> (24 - ((i + 2) % 4) * 8)) & 0xff;
9607
9608                var triplet = (byte1 << 16) | (byte2 << 8) | byte3;
9609
9610                for (var j = 0; (j < 4) && (i + j * 0.75 < sigBytes); j++) {
9611                    base64Chars.push(map.charAt((triplet >>> (6 * (3 - j))) & 0x3f));
9612                }
9613            }
9614
9615            // Add padding
9616            var paddingChar = map.charAt(64);
9617            if (paddingChar) {
9618                while (base64Chars.length % 4) {
9619                    base64Chars.push(paddingChar);
9620                }
9621            }
9622
9623            return base64Chars.join('');
9624        },
9625
9626        /**
9627         * Converts a Base64 string to a word array.
9628         *
9629         * @param {string} base64Str The Base64 string.
9630         *
9631         * @return {WordArray} The word array.
9632         *
9633         * @static
9634         *
9635         * @example
9636         *
9637         *     var wordArray = CryptoJS.enc.Base64.parse(base64String);
9638         */
9639        parse: function (base64Str) {
9640            // Shortcuts
9641            var base64StrLength = base64Str.length;
9642            var map = this._map;
9643
9644            // Ignore padding
9645            var paddingChar = map.charAt(64);
9646            if (paddingChar) {
9647                var paddingIndex = base64Str.indexOf(paddingChar);
9648                if (paddingIndex != -1) {
9649                    base64StrLength = paddingIndex;
9650                }
9651            }
9652
9653            // Convert
9654            var words = [];
9655            var nBytes = 0;
9656            for (var i = 0; i < base64StrLength; i++) {
9657                if (i % 4) {
9658                    var bits1 = map.indexOf(base64Str.charAt(i - 1)) << ((i % 4) * 2);
9659                    var bits2 = map.indexOf(base64Str.charAt(i)) >>> (6 - (i % 4) * 2);
9660                    words[nBytes >>> 2] |= (bits1 | bits2) << (24 - (nBytes % 4) * 8);
9661                    nBytes++;
9662                }
9663            }
9664
9665            return WordArray.create(words, nBytes);
9666        },
9667
9668        _map: 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/='
9669    };
9670}());
9671
9672/*
9673CryptoJS v3.1.2
9674code.google.com/p/crypto-js
9675(c) 2009-2013 by Jeff Mott. All rights reserved.
9676code.google.com/p/crypto-js/wiki/License
9677*/
9678(function () {
9679    // Check if typed arrays are supported
9680    if (typeof ArrayBuffer != 'function') {
9681        return;
9682    }
9683
9684    // Shortcuts
9685    var C = CryptoJS;
9686    var C_lib = C.lib;
9687    var WordArray = C_lib.WordArray;
9688
9689    // Reference original init
9690    var superInit = WordArray.init;
9691
9692    // Augment WordArray.init to handle typed arrays
9693    var subInit = WordArray.init = function (typedArray) {
9694        // Convert buffers to uint8
9695        if (typedArray instanceof ArrayBuffer) {
9696            typedArray = new Uint8Array(typedArray);
9697        }
9698
9699        // Convert other array views to uint8
9700        if (
9701            typedArray instanceof Int8Array ||
9702            typedArray instanceof Uint8ClampedArray ||
9703            typedArray instanceof Int16Array ||
9704            typedArray instanceof Uint16Array ||
9705            typedArray instanceof Int32Array ||
9706            typedArray instanceof Uint32Array ||
9707            typedArray instanceof Float32Array ||
9708            typedArray instanceof Float64Array
9709        ) {
9710            typedArray = new Uint8Array(typedArray.buffer, typedArray.byteOffset, typedArray.byteLength);
9711        }
9712
9713        // Handle Uint8Array
9714        if (typedArray instanceof Uint8Array) {
9715            // Shortcut
9716            var typedArrayByteLength = typedArray.byteLength;
9717
9718            // Extract bytes
9719            var words = [];
9720            for (var i = 0; i < typedArrayByteLength; i++) {
9721                words[i >>> 2] |= typedArray[i] << (24 - (i % 4) * 8);
9722            }
9723
9724            // Initialize this word array
9725            superInit.call(this, words, typedArrayByteLength);
9726        } else {
9727            // Else call normal init
9728            superInit.apply(this, arguments);
9729        }
9730    };
9731
9732    subInit.prototype = WordArray;
9733}());
9734
9735/*!
9736    Copyright 2012 Rustici Software
9737
9738    Licensed under the Apache License, Version 2.0 (the "License");
9739    you may not use this file except in compliance with the License.
9740    You may obtain a copy of the License at
9741
9742    http://www.apache.org/licenses/LICENSE-2.0
9743
9744    Unless required by applicable law or agreed to in writing, software
9745    distributed under the License is distributed on an "AS IS" BASIS,
9746    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9747    See the License for the specific language governing permissions and
9748    limitations under the License.
9749*/
9750
9751/**
9752TinCan client library
9753
9754TODO:
9755
9756* Add statement queueing
9757
9758@module TinCan
9759**/
9760var TinCan;
9761
9762(function () {
9763    "use strict";
9764    var _reservedQSParams = {
9765        //
9766        // these are TC spec reserved words that may end up in queries to the endpoint
9767        //
9768        statementId:       true,
9769        voidedStatementId: true,
9770        verb:              true,
9771        object:            true,
9772        registration:      true,
9773        context:           true,
9774        actor:             true,
9775        since:             true,
9776        until:             true,
9777        limit:             true,
9778        authoritative:     true,
9779        sparse:            true,
9780        instructor:        true,
9781        ascending:         true,
9782        continueToken:     true,
9783        agent:             true,
9784        activityId:        true,
9785        stateId:           true,
9786        profileId:         true,
9787
9788        //
9789        // these are suggested by the LMS launch spec addition that TinCanJS consumes
9790        //
9791        activity_platform: true,
9792        grouping:          true,
9793        "Accept-Language": true
9794    };
9795
9796    /**
9797    @class TinCan
9798    @constructor
9799    @param {Object} [options] Configuration used to initialize.
9800        @param {String} [options.url] URL for determining launch provided
9801            configuration options
9802        @param {Array} [options.recordStores] list of pre-configured LRSes
9803        @param {Object|TinCan.Agent} [options.actor] default actor
9804        @param {Object|TinCan.Activity} [options.activity] default activity
9805        @param {String} [options.registration] default registration
9806        @param {Object|TinCan.Context} [options.context] default context
9807    **/
9808    TinCan = function (cfg) {
9809        this.log("constructor");
9810
9811        /**
9812        @property recordStores
9813        @type Array
9814        */
9815        this.recordStores = [];
9816
9817        /**
9818        Default actor used when preparing statements that
9819        don't yet have an actor set, and for saving state, etc.
9820
9821        @property actor
9822        @type Object
9823        */
9824        this.actor = null;
9825
9826        /**
9827        Default activity, may be used as a statement 'target'
9828        or incorporated into 'context'
9829
9830        @property activity
9831        @type Object
9832        */
9833        this.activity = null;
9834
9835        /**
9836        Default registration, included in default context when
9837        provided, otherwise used in statement queries
9838
9839        @property registration
9840        @type String
9841        */
9842        this.registration = null;
9843
9844        /**
9845        Default context used when preparing statements that
9846        don't yet have a context set, or mixed in when one
9847        has been provided, properties do NOT override on mixing
9848
9849        @property context
9850        @type Object
9851        */
9852        this.context = null;
9853
9854        this.init(cfg);
9855    };
9856
9857    TinCan.prototype = {
9858        LOG_SRC: "TinCan",
9859
9860        /**
9861        Safe version of logging, only displays when .DEBUG is true, and console.log
9862        is available
9863
9864        @method log
9865        @param {String} msg Message to output
9866        */
9867        log: function (msg, src) {
9868            /* globals console */
9869            if (TinCan.DEBUG && typeof console !== "undefined" && console.log) {
9870                src = src || this.LOG_SRC || "TinCan";
9871
9872                console.log("TinCan." + src + ": " + msg);
9873            }
9874        },
9875
9876        /**
9877        @method init
9878        @param {Object} [options] Configuration used to initialize (see TinCan constructor).
9879        */
9880        init: function (cfg) {
9881            this.log("init");
9882            var i;
9883
9884            cfg = cfg || {};
9885
9886            if (cfg.hasOwnProperty("url") && cfg.url !== "") {
9887                this._initFromQueryString(cfg.url);
9888            }
9889
9890            if (cfg.hasOwnProperty("recordStores") && cfg.recordStores !== undefined) {
9891                for (i = 0; i < cfg.recordStores.length; i += 1) {
9892                    this.addRecordStore(cfg.recordStores[i]);
9893                }
9894            }
9895            if (cfg.hasOwnProperty("activity")) {
9896                if (cfg.activity instanceof TinCan.Activity) {
9897                    this.activity = cfg.activity;
9898                }
9899                else {
9900                    this.activity = new TinCan.Activity (cfg.activity);
9901                }
9902            }
9903            if (cfg.hasOwnProperty("actor")) {
9904                if (cfg.actor instanceof TinCan.Agent) {
9905                    this.actor = cfg.actor;
9906                }
9907                else {
9908                    this.actor = new TinCan.Agent (cfg.actor);
9909                }
9910            }
9911            if (cfg.hasOwnProperty("context")) {
9912                if (cfg.context instanceof TinCan.Context) {
9913                    this.context = cfg.context;
9914                }
9915                else {
9916                    this.context = new TinCan.Context (cfg.context);
9917                }
9918            }
9919            if (cfg.hasOwnProperty("registration")) {
9920                this.registration = cfg.registration;
9921            }
9922        },
9923
9924        /**
9925        @method _initFromQueryString
9926        @param {String} url
9927        @private
9928        */
9929        _initFromQueryString: function (url) {
9930            this.log("_initFromQueryString");
9931
9932            var i,
9933                prop,
9934                qsParams = TinCan.Utils.parseURL(url).params,
9935                lrsProps = ["endpoint", "auth"],
9936                lrsCfg = {},
9937                contextCfg,
9938                extended = null
9939            ;
9940
9941            if (qsParams.hasOwnProperty("actor")) {
9942                this.log("_initFromQueryString - found actor: " + qsParams.actor);
9943                try {
9944                    this.actor = TinCan.Agent.fromJSON(qsParams.actor);
9945                    delete qsParams.actor;
9946                }
9947                catch (ex) {
9948                    this.log("_initFromQueryString - failed to set actor: " + ex);
9949                }
9950            }
9951
9952            if (qsParams.hasOwnProperty("activity_id")) {
9953                this.activity = new TinCan.Activity (
9954                    {
9955                        id: qsParams.activity_id
9956                    }
9957                );
9958                delete qsParams.activity_id;
9959            }
9960
9961            if (
9962                qsParams.hasOwnProperty("activity_platform") ||
9963                qsParams.hasOwnProperty("registration") ||
9964                qsParams.hasOwnProperty("grouping")
9965            ) {
9966                contextCfg = {};
9967
9968                if (qsParams.hasOwnProperty("activity_platform")) {
9969                    contextCfg.platform = qsParams.activity_platform;
9970                    delete qsParams.activity_platform;
9971                }
9972                if (qsParams.hasOwnProperty("registration")) {
9973                    //
9974                    // stored in two locations cause we always want it in the default
9975                    // context, but we also want to be able to get to it for Statement
9976                    // queries
9977                    //
9978                    contextCfg.registration = this.registration = qsParams.registration;
9979                    delete qsParams.registration;
9980                }
9981                if (qsParams.hasOwnProperty("grouping")) {
9982                    contextCfg.contextActivities = {};
9983                    contextCfg.contextActivities.grouping = qsParams.grouping;
9984                    delete qsParams.grouping;
9985                }
9986
9987                this.context = new TinCan.Context (contextCfg);
9988            }
9989
9990            //
9991            // order matters here, process the URL provided LRS last because it gets
9992            // all the remaining parameters so that they get passed through
9993            //
9994            if (qsParams.hasOwnProperty("endpoint")) {
9995                for (i = 0; i < lrsProps.length; i += 1) {
9996                    prop = lrsProps[i];
9997                    if (qsParams.hasOwnProperty(prop)) {
9998                        lrsCfg[prop] = qsParams[prop];
9999                        delete qsParams[prop];
10000                    }
10001                }
10002
10003                // remove our reserved params so they don't end up  in the extended object
10004                for (i in qsParams) {
10005                    if (qsParams.hasOwnProperty(i)) {
10006                        if (_reservedQSParams.hasOwnProperty(i)) {
10007                            delete qsParams[i];
10008                        } else {
10009                            extended = extended || {};
10010                            extended[i] = qsParams[i];
10011                        }
10012                    }
10013                }
10014                if (extended !== null) {
10015                    lrsCfg.extended = extended;
10016                }
10017
10018                lrsCfg.allowFail = false;
10019
10020                this.addRecordStore(lrsCfg);
10021            }
10022        },
10023
10024        /**
10025        @method addRecordStore
10026        @param {Object} Configuration data
10027
10028         * TODO:
10029         * check endpoint for trailing '/'
10030         * check for unique endpoints
10031        */
10032        addRecordStore: function (cfg) {
10033            this.log("addRecordStore");
10034            var lrs;
10035            if (cfg instanceof TinCan.LRS) {
10036                lrs = cfg;
10037            }
10038            else {
10039                lrs = new TinCan.LRS (cfg);
10040            }
10041            this.recordStores.push(lrs);
10042        },
10043
10044        /**
10045        @method prepareStatement
10046        @param {Object|TinCan.Statement} Base statement properties or
10047            pre-created TinCan.Statement instance
10048        @return {TinCan.Statement}
10049        */
10050        prepareStatement: function (stmt) {
10051            this.log("prepareStatement");
10052            if (! (stmt instanceof TinCan.Statement)) {
10053                stmt = new TinCan.Statement (stmt);
10054            }
10055
10056            if (stmt.actor === null && this.actor !== null) {
10057                stmt.actor = this.actor;
10058            }
10059            if (stmt.target === null && this.activity !== null) {
10060                stmt.target = this.activity;
10061            }
10062
10063            if (this.context !== null) {
10064                if (stmt.context === null) {
10065                    stmt.context = this.context;
10066                }
10067                else {
10068                    if (stmt.context.registration === null) {
10069                        stmt.context.registration = this.context.registration;
10070                    }
10071                    if (stmt.context.platform === null) {
10072                        stmt.context.platform = this.context.platform;
10073                    }
10074
10075                    if (this.context.contextActivities !== null) {
10076                        if (stmt.context.contextActivities === null) {
10077                            stmt.context.contextActivities = this.context.contextActivities;
10078                        }
10079                        else {
10080                            if (this.context.contextActivities.grouping !== null && stmt.context.contextActivities.grouping === null) {
10081                                stmt.context.contextActivities.grouping = this.context.contextActivities.grouping;
10082                            }
10083                            if (this.context.contextActivities.parent !== null && stmt.context.contextActivities.parent === null) {
10084                                stmt.context.contextActivities.parent = this.context.contextActivities.parent;
10085                            }
10086                            if (this.context.contextActivities.other !== null && stmt.context.contextActivities.other === null) {
10087                                stmt.context.contextActivities.other = this.context.contextActivities.other;
10088                            }
10089                        }
10090                    }
10091                }
10092            }
10093
10094            return stmt;
10095        },
10096
10097        /**
10098        Calls saveStatement on each configured LRS, provide callback to make it asynchronous
10099
10100        @method sendStatement
10101        @param {TinCan.Statement|Object} statement Send statement to LRS
10102        @param {Function} [callback] Callback function to execute on completion
10103        */
10104        sendStatement: function (stmt, callback) {
10105            this.log("sendStatement");
10106
10107            // would prefer to use .bind instead of 'self'
10108            var self = this,
10109                lrs,
10110                statement = this.prepareStatement(stmt),
10111                rsCount = this.recordStores.length,
10112                i,
10113                results = [],
10114                callbackWrapper,
10115                callbackResults = []
10116            ;
10117
10118            if (rsCount > 0) {
10119                /*
10120                   if there is a callback that is a function then we need
10121                   to wrap that function with a function that becomes
10122                   the new callback that reduces a closure count of the
10123                   requests that don't have allowFail set to true and
10124                   when that number hits zero then the original callback
10125                   is executed
10126                */
10127                if (typeof callback === "function") {
10128                    callbackWrapper = function (err, xhr) {
10129                        var args;
10130
10131                        self.log("sendStatement - callbackWrapper: " + rsCount);
10132                        if (rsCount > 1) {
10133                            rsCount -= 1;
10134                            callbackResults.push(
10135                                {
10136                                    err: err,
10137                                    xhr: xhr
10138                                }
10139                            );
10140                        }
10141                        else if (rsCount === 1) {
10142                            callbackResults.push(
10143                                {
10144                                    err: err,
10145                                    xhr: xhr
10146                                }
10147                            );
10148                            args = [
10149                                callbackResults,
10150                                statement
10151                            ];
10152                            callback.apply(this, args);
10153                        }
10154                        else {
10155                            self.log("sendStatement - unexpected record store count: " + rsCount);
10156                        }
10157                    };
10158                }
10159
10160                for (i = 0; i < rsCount; i += 1) {
10161                    lrs = this.recordStores[i];
10162
10163                    results.push(
10164                        lrs.saveStatement(statement, { callback: callbackWrapper })
10165                    );
10166                }
10167            }
10168            else {
10169                this.log("[warning] sendStatement: No LRSs added yet (statement not sent)");
10170                if (typeof callback === "function") {
10171                    callback.apply(this, [ null, statement ]);
10172                }
10173            }
10174
10175            return {
10176                statement: statement,
10177                results: results
10178            };
10179        },
10180
10181        /**
10182        Calls retrieveStatement on the first LRS, provide callback to make it asynchronous
10183
10184        @method getStatement
10185        @param {String} [stmtId] Statement ID to get
10186        @param {Function} [callback] Callback function to execute on completion
10187        @param {Object} [cfg] Configuration data
10188            @param {Object} [params] Query parameters
10189                @param {Boolean} [attachments] Include attachments in multipart response or don't (defualt: false)
10190        @return {Array|Result} Array of results, or single result
10191
10192        TODO: make TinCan track statements it has seen in a local cache to be returned easily
10193        */
10194        getStatement: function (stmtId, callback, cfg) {
10195            this.log("getStatement");
10196
10197            var lrs;
10198
10199            cfg = cfg || {};
10200            cfg.params = cfg.params || {};
10201
10202            if (this.recordStores.length > 0) {
10203                //
10204                // for statements (for now) we only need to read from the first LRS
10205                // in the future it may make sense to get all from all LRSes and
10206                // compare to remove duplicates or allow inspection of them for differences?
10207                //
10208                // TODO: make this the first non-allowFail LRS but for now it should
10209                // be good enough to make it the first since we know the LMS provided
10210                // LRS is the first
10211                //
10212                lrs = this.recordStores[0];
10213
10214                return lrs.retrieveStatement(stmtId, { callback: callback, params: cfg.params });
10215            }
10216
10217            this.log("[warning] getStatement: No LRSs added yet (statement not retrieved)");
10218        },
10219
10220        /**
10221        Creates a statement used for voiding the passed statement/statement ID and calls
10222        send statement with the voiding statement.
10223
10224        @method voidStatement
10225        @param {TinCan.Statement|String} statement Statement or statement ID to void
10226        @param {Function} [callback] Callback function to execute on completion
10227        @param {Object} [options] Options used to build voiding statement
10228            @param {TinCan.Agent} [options.actor] Agent to be used as 'actor' in voiding statement
10229        */
10230        voidStatement: function (stmt, callback, options) {
10231            this.log("voidStatement");
10232
10233            // would prefer to use .bind instead of 'self'
10234            var self = this,
10235                lrs,
10236                actor,
10237                voidingStatement,
10238                rsCount = this.recordStores.length,
10239                i,
10240                results = [],
10241                callbackWrapper,
10242                callbackResults = []
10243            ;
10244
10245            if (stmt instanceof TinCan.Statement) {
10246                stmt = stmt.id;
10247            }
10248
10249            if (typeof options.actor !== "undefined") {
10250                actor = options.actor;
10251            }
10252            else if (this.actor !== null) {
10253                actor = this.actor;
10254            }
10255
10256            voidingStatement = new TinCan.Statement(
10257                {
10258                    actor: actor,
10259                    verb: {
10260                        id: "http://adlnet.gov/expapi/verbs/voided"
10261                    },
10262                    target: {
10263                        objectType: "StatementRef",
10264                        id: stmt
10265                    }
10266                }
10267            );
10268
10269            if (rsCount > 0) {
10270                /*
10271                   if there is a callback that is a function then we need
10272                   to wrap that function with a function that becomes
10273                   the new callback that reduces a closure count of the
10274                   requests that don't have allowFail set to true and
10275                   when that number hits zero then the original callback
10276                   is executed
10277                */
10278                if (typeof callback === "function") {
10279                    callbackWrapper = function (err, xhr) {
10280                        var args;
10281
10282                        self.log("voidStatement - callbackWrapper: " + rsCount);
10283                        if (rsCount > 1) {
10284                            rsCount -= 1;
10285                            callbackResults.push(
10286                                {
10287                                    err: err,
10288                                    xhr: xhr
10289                                }
10290                            );
10291                        }
10292                        else if (rsCount === 1) {
10293                            callbackResults.push(
10294                                {
10295                                    err: err,
10296                                    xhr: xhr
10297                                }
10298                            );
10299                            args = [
10300                                callbackResults,
10301                                voidingStatement
10302                            ];
10303                            callback.apply(this, args);
10304                        }
10305                        else {
10306                            self.log("voidStatement - unexpected record store count: " + rsCount);
10307                        }
10308                    };
10309                }
10310
10311                for (i = 0; i < rsCount; i += 1) {
10312                    lrs = this.recordStores[i];
10313
10314                    results.push(
10315                        lrs.saveStatement(voidingStatement, { callback: callbackWrapper })
10316                    );
10317                }
10318            }
10319            else {
10320                this.log("[warning] voidStatement: No LRSs added yet (statement not sent)");
10321                if (typeof callback === "function") {
10322                    callback.apply(this, [ null, voidingStatement ]);
10323                }
10324            }
10325
10326            return {
10327                statement: voidingStatement,
10328                results: results
10329            };
10330        },
10331
10332        /**
10333        Calls retrieveVoidedStatement on the first LRS, provide callback to make it asynchronous
10334
10335        @method getVoidedStatement
10336        @param {String} statement Statement ID to get
10337        @param {Function} [callback] Callback function to execute on completion
10338        @return {Array|Result} Array of results, or single result
10339
10340        TODO: make TinCan track voided statements it has seen in a local cache to be returned easily
10341        */
10342        getVoidedStatement: function (stmtId, callback) {
10343            this.log("getVoidedStatement");
10344
10345            var lrs;
10346
10347            if (this.recordStores.length > 0) {
10348                //
10349                // for statements (for now) we only need to read from the first LRS
10350                // in the future it may make sense to get all from all LRSes and
10351                // compare to remove duplicates or allow inspection of them for differences?
10352                //
10353                // TODO: make this the first non-allowFail LRS but for now it should
10354                // be good enough to make it the first since we know the LMS provided
10355                // LRS is the first
10356                //
10357                lrs = this.recordStores[0];
10358
10359                return lrs.retrieveVoidedStatement(stmtId, { callback: callback });
10360            }
10361
10362            this.log("[warning] getVoidedStatement: No LRSs added yet (statement not retrieved)");
10363        },
10364
10365        /**
10366        Calls saveStatements with list of prepared statements
10367
10368        @method sendStatements
10369        @param {Array} Array of statements to send
10370        @param {Function} Callback function to execute on completion
10371        */
10372        sendStatements: function (stmts, callback) {
10373            this.log("sendStatements");
10374            var self = this,
10375                lrs,
10376                statements = [],
10377                rsCount = this.recordStores.length,
10378                i,
10379                results = [],
10380                callbackWrapper,
10381                callbackResults = []
10382            ;
10383            if (stmts.length === 0) {
10384                if (typeof callback === "function") {
10385                    callback.apply(this, [ null, statements ]);
10386                }
10387            }
10388            else {
10389                for (i = 0; i < stmts.length; i += 1) {
10390                    statements.push(
10391                        this.prepareStatement(stmts[i])
10392                    );
10393                }
10394
10395                if (rsCount > 0) {
10396                    /*
10397                       if there is a callback that is a function then we need
10398                       to wrap that function with a function that becomes
10399                       the new callback that reduces a closure count of the
10400                       requests that don't have allowFail set to true and
10401                       when that number hits zero then the original callback
10402                       is executed
10403                    */
10404
10405                    if (typeof callback === "function") {
10406                        callbackWrapper = function (err, xhr) {
10407                            var args;
10408
10409                            self.log("sendStatements - callbackWrapper: " + rsCount);
10410                            if (rsCount > 1) {
10411                                rsCount -= 1;
10412                                callbackResults.push(
10413                                    {
10414                                        err: err,
10415                                        xhr: xhr
10416                                    }
10417                                );
10418                            }
10419                            else if (rsCount === 1) {
10420                                callbackResults.push(
10421                                    {
10422                                        err: err,
10423                                        xhr: xhr
10424                                    }
10425                                );
10426                                args = [
10427                                    callbackResults,
10428                                    statements
10429                                ];
10430                                callback.apply(this, args);
10431                            }
10432                            else {
10433                                self.log("sendStatements - unexpected record store count: " + rsCount);
10434                            }
10435                        };
10436                    }
10437
10438                    for (i = 0; i < rsCount; i += 1) {
10439                        lrs = this.recordStores[i];
10440
10441                        results.push(
10442                            lrs.saveStatements(statements, { callback: callbackWrapper })
10443                        );
10444                    }
10445                }
10446                else {
10447                    this.log("[warning] sendStatements: No LRSs added yet (statements not sent)");
10448                    if (typeof callback === "function") {
10449                        callback.apply(this, [ null, statements ]);
10450                    }
10451                }
10452            }
10453
10454            return {
10455                statements: statements,
10456                results: results
10457            };
10458        },
10459
10460        /**
10461        @method getStatements
10462        @param {Object} [cfg] Configuration for request
10463            @param {Boolean} [cfg.sendActor] Include default actor in query params
10464            @param {Boolean} [cfg.sendActivity] Include default activity in query params
10465            @param {Object} [cfg.params] Parameters used to filter.
10466                            These are the same as those accepted by the
10467                            <a href="TinCan.LRS.html#method_queryStatements">LRS.queryStatements</a>
10468                            method.
10469
10470            @param {Function} [cfg.callback] Function to run at completion
10471
10472        TODO: support multiple LRSs and flag to use single
10473        */
10474        getStatements: function (cfg) {
10475            this.log("getStatements");
10476            var queryCfg = {},
10477                lrs,
10478                params
10479            ;
10480            if (this.recordStores.length > 0) {
10481                //
10482                // for get (for now) we only get from one (as they should be the same)
10483                // but it may make sense to long term try to merge statements, perhaps
10484                // by using statementId as unique
10485                //
10486                // TODO: make this the first non-allowFail LRS but for now it should
10487                // be good enough to make it the first since we know the LMS provided
10488                // LRS is the first
10489                //
10490                lrs = this.recordStores[0];
10491
10492                cfg = cfg || {};
10493
10494                // TODO: need a clone function?
10495                params = cfg.params || {};
10496
10497                if (cfg.sendActor && this.actor !== null) {
10498                    if (lrs.version === "0.9" || lrs.version === "0.95") {
10499                        params.actor = this.actor;
10500                    }
10501                    else {
10502                        params.agent = this.actor;
10503                    }
10504                }
10505                if (cfg.sendActivity && this.activity !== null) {
10506                    if (lrs.version === "0.9" || lrs.version === "0.95") {
10507                        params.target = this.activity;
10508                    }
10509                    else {
10510                        params.activity = this.activity;
10511                    }
10512                }
10513                if (typeof params.registration === "undefined" && this.registration !== null) {
10514                    params.registration = this.registration;
10515                }
10516
10517                queryCfg = {
10518                    params: params
10519                };
10520                if (typeof cfg.callback !== "undefined") {
10521                    queryCfg.callback = cfg.callback;
10522                }
10523
10524                return lrs.queryStatements(queryCfg);
10525            }
10526
10527            this.log("[warning] getStatements: No LRSs added yet (statements not read)");
10528        },
10529
10530        /**
10531        @method getState
10532        @param {String} key Key to retrieve from the state
10533        @param {Object} [cfg] Configuration for request
10534            @param {Object} [cfg.agent] Agent used in query,
10535                defaults to 'actor' property if empty
10536            @param {Object} [cfg.activity] Activity used in query,
10537                defaults to 'activity' property if empty
10538            @param {Object} [cfg.registration] Registration used in query,
10539                defaults to 'registration' property if empty
10540            @param {Function} [cfg.callback] Function to run with state
10541        */
10542        getState: function (key, cfg) {
10543            this.log("getState");
10544            var queryCfg,
10545                lrs
10546            ;
10547
10548            if (this.recordStores.length > 0) {
10549                //
10550                // for state (for now) we are only going to store to the first LRS
10551                // so only get from there too
10552                //
10553                // TODO: make this the first non-allowFail LRS but for now it should
10554                // be good enough to make it the first since we know the LMS provided
10555                // LRS is the first
10556                //
10557                lrs = this.recordStores[0];
10558
10559                cfg = cfg || {};
10560
10561                queryCfg = {
10562                    agent: (typeof cfg.agent !== "undefined" ? cfg.agent : this.actor),
10563                    activity: (typeof cfg.activity !== "undefined" ? cfg.activity : this.activity)
10564                };
10565                if (typeof cfg.registration !== "undefined") {
10566                    queryCfg.registration = cfg.registration;
10567                }
10568                else if (this.registration !== null) {
10569                    queryCfg.registration = this.registration;
10570                }
10571                if (typeof cfg.callback !== "undefined") {
10572                    queryCfg.callback = cfg.callback;
10573                }
10574
10575                return lrs.retrieveState(key, queryCfg);
10576            }
10577
10578            this.log("[warning] getState: No LRSs added yet (state not retrieved)");
10579        },
10580
10581        /**
10582        @method setState
10583        @param {String} key Key to store into the state
10584        @param {String|Object} val Value to store into the state, objects will be stringified to JSON
10585        @param {Object} [cfg] Configuration for request
10586            @param {Object} [cfg.agent] Agent used in query,
10587                defaults to 'actor' property if empty
10588            @param {Object} [cfg.activity] Activity used in query,
10589                defaults to 'activity' property if empty
10590            @param {Object} [cfg.registration] Registration used in query,
10591                defaults to 'registration' property if empty
10592            @param {String} [cfg.lastSHA1] SHA1 of the previously seen existing state
10593            @param {String} [cfg.contentType] Content-Type to specify in headers
10594            @param {Boolean} [cfg.overwriteJSON] If the Content-Type is JSON, should a PUT be used?
10595            @param {Function} [cfg.callback] Function to run with state
10596        */
10597        setState: function (key, val, cfg) {
10598            this.log("setState");
10599            var queryCfg,
10600                lrs
10601            ;
10602
10603            if (this.recordStores.length > 0) {
10604                //
10605                // for state (for now) we are only going to store to the first LRS
10606                // so only get from there too
10607                //
10608                // TODO: make this the first non-allowFail LRS but for now it should
10609                // be good enough to make it the first since we know the LMS provided
10610                // LRS is the first
10611                //
10612                lrs = this.recordStores[0];
10613
10614                cfg = cfg || {};
10615
10616                queryCfg = {
10617                    agent: (typeof cfg.agent !== "undefined" ? cfg.agent : this.actor),
10618                    activity: (typeof cfg.activity !== "undefined" ? cfg.activity : this.activity)
10619                };
10620                if (typeof cfg.registration !== "undefined") {
10621                    queryCfg.registration = cfg.registration;
10622                }
10623                else if (this.registration !== null) {
10624                    queryCfg.registration = this.registration;
10625                }
10626                if (typeof cfg.lastSHA1 !== "undefined") {
10627                    queryCfg.lastSHA1 = cfg.lastSHA1;
10628                }
10629                if (typeof cfg.contentType !== "undefined") {
10630                    queryCfg.contentType = cfg.contentType;
10631                    if ((typeof cfg.overwriteJSON !== "undefined") && (! cfg.overwriteJSON) && (TinCan.Utils.isApplicationJSON(cfg.contentType))) {
10632                        queryCfg.method = "POST";
10633                    }
10634                }
10635                if (typeof cfg.callback !== "undefined") {
10636                    queryCfg.callback = cfg.callback;
10637                }
10638
10639                return lrs.saveState(key, val, queryCfg);
10640            }
10641
10642            this.log("[warning] setState: No LRSs added yet (state not saved)");
10643        },
10644
10645        /**
10646        @method deleteState
10647        @param {String|null} key Key to remove from the state, or null to clear all
10648        @param {Object} [cfg] Configuration for request
10649            @param {Object} [cfg.agent] Agent used in query,
10650                defaults to 'actor' property if empty
10651            @param {Object} [cfg.activity] Activity used in query,
10652                defaults to 'activity' property if empty
10653            @param {Object} [cfg.registration] Registration used in query,
10654                defaults to 'registration' property if empty
10655            @param {Function} [cfg.callback] Function to run with state
10656        */
10657        deleteState: function (key, cfg) {
10658            this.log("deleteState");
10659            var queryCfg,
10660                lrs
10661            ;
10662
10663            if (this.recordStores.length > 0) {
10664                //
10665                // for state (for now) we are only going to store to the first LRS
10666                // so only get from there too
10667                //
10668                // TODO: make this the first non-allowFail LRS but for now it should
10669                // be good enough to make it the first since we know the LMS provided
10670                // LRS is the first
10671                //
10672                lrs = this.recordStores[0];
10673
10674                cfg = cfg || {};
10675
10676                queryCfg = {
10677                    agent: (typeof cfg.agent !== "undefined" ? cfg.agent : this.actor),
10678                    activity: (typeof cfg.activity !== "undefined" ? cfg.activity : this.activity)
10679                };
10680                if (typeof cfg.registration !== "undefined") {
10681                    queryCfg.registration = cfg.registration;
10682                }
10683                else if (this.registration !== null) {
10684                    queryCfg.registration = this.registration;
10685                }
10686                if (typeof cfg.callback !== "undefined") {
10687                    queryCfg.callback = cfg.callback;
10688                }
10689
10690                return lrs.dropState(key, queryCfg);
10691            }
10692
10693            this.log("[warning] deleteState: No LRSs added yet (state not deleted)");
10694        },
10695
10696        /**
10697        @method getActivityProfile
10698        @param {String} key Key to retrieve from the profile
10699        @param {Object} [cfg] Configuration for request
10700            @param {Object} [cfg.activity] Activity used in query,
10701                defaults to 'activity' property if empty
10702            @param {Function} [cfg.callback] Function to run with activity profile
10703        */
10704        getActivityProfile: function (key, cfg) {
10705            this.log("getActivityProfile");
10706            var queryCfg,
10707                lrs
10708            ;
10709
10710            if (this.recordStores.length > 0) {
10711                //
10712                // for activity profiles (for now) we are only going to store to the first LRS
10713                // so only get from there too
10714                //
10715                // TODO: make this the first non-allowFail LRS but for now it should
10716                // be good enough to make it the first since we know the LMS provided
10717                // LRS is the first
10718                //
10719                lrs = this.recordStores[0];
10720
10721                cfg = cfg || {};
10722
10723                queryCfg = {
10724                    activity: (typeof cfg.activity !== "undefined" ? cfg.activity : this.activity)
10725                };
10726                if (typeof cfg.callback !== "undefined") {
10727                    queryCfg.callback = cfg.callback;
10728                }
10729
10730                return lrs.retrieveActivityProfile(key, queryCfg);
10731            }
10732
10733            this.log("[warning] getActivityProfile: No LRSs added yet (activity profile not retrieved)");
10734        },
10735
10736        /**
10737        @method setActivityProfile
10738        @param {String}
10738 key Key to store into the activity profile
10739        @param {String|Object} val Value to store into the activity profile, objects will be stringified to JSON
10740        @param {Object} [cfg] Configuration for request
10741            @param {Object} [cfg.activity] Activity used in query,
10742                defaults to 'activity' property if empty
10743            @param {String} [cfg.lastSHA1] SHA1 of the previously seen existing profile
10744            @param {String} [cfg.contentType] Content-Type to specify in headers
10745            @param {Boolean} [cfg.overwriteJSON] If the Content-Type is JSON, should a PUT be used?
10746            @param {Function} [cfg.callback] Function to run with activity profile
10747        */
10748        setActivityProfile: function (key, val, cfg) {
10749            this.log("setActivityProfile");
10750            var queryCfg,
10751                lrs
10752            ;
10753
10754            if (this.recordStores.length > 0) {
10755                //
10756                // for activity profile (for now) we are only going to store to the first LRS
10757                // so only get from there too
10758                //
10759                // TODO: make this the first non-allowFail LRS but for now it should
10760                // be good enough to make it the first since we know the LMS provided
10761                // LRS is the first
10762                //
10763                lrs = this.recordStores[0];
10764
10765                cfg = cfg || {};
10766
10767                queryCfg = {
10768                    activity: (typeof cfg.activity !== "undefined" ? cfg.activity : this.activity)
10769                };
10770                if (typeof cfg.callback !== "undefined") {
10771                    queryCfg.callback = cfg.callback;
10772                }
10773                if (typeof cfg.lastSHA1 !== "undefined") {
10774                    queryCfg.lastSHA1 = cfg.lastSHA1;
10775                }
10776                if (typeof cfg.contentType !== "undefined") {
10777                    queryCfg.contentType = cfg.contentType;
10778                    if ((typeof cfg.overwriteJSON !== "undefined") && (! cfg.overwriteJSON) && (TinCan.Utils.isApplicationJSON(cfg.contentType))) {
10779                        queryCfg.method = "POST";
10780                    }
10781                }
10782
10783                return lrs.saveActivityProfile(key, val, queryCfg);
10784            }
10785
10786            this.log("[warning] setActivityProfile: No LRSs added yet (activity profile not saved)");
10787        },
10788
10789        /**
10790        @method deleteActivityProfile
10791        @param {String|null} key Key to remove from the activity profile, or null to clear all
10792        @param {Object} [cfg] Configuration for request
10793            @param {Object} [cfg.activity] Activity used in query,
10794                defaults to 'activity' property if empty
10795            @param {Function} [cfg.callback] Function to run with activity profile
10796        */
10797        deleteActivityProfile: function (key, cfg) {
10798            this.log("deleteActivityProfile");
10799            var queryCfg,
10800                lrs
10801            ;
10802
10803            if (this.recordStores.length > 0) {
10804                //
10805                // for activity profile (for now) we are only going to store to the first LRS
10806                // so only get from there too
10807                //
10808                // TODO: make this the first non-allowFail LRS but for now it should
10809                // be good enough to make it the first since we know the LMS provided
10810                // LRS is the first
10811                //
10812                lrs = this.recordStores[0];
10813
10814                cfg = cfg || {};
10815
10816                queryCfg = {
10817                    activity: (typeof cfg.activity !== "undefined" ? cfg.activity : this.activity)
10818                };
10819                if (typeof cfg.callback !== "undefined") {
10820                    queryCfg.callback = cfg.callback;
10821                }
10822
10823                return lrs.dropActivityProfile(key, queryCfg);
10824            }
10825
10826            this.log("[warning] deleteActivityProfile: No LRSs added yet (activity profile not deleted)");
10827        },
10828
10829        /**
10830        @method getAgentProfile
10831        @param {String} key Key to retrieve from the profile
10832        @param {Object} [cfg] Configuration for request
10833            @param {Object} [cfg.agent] Agent used in query,
10834                defaults to 'actor' property if empty
10835            @param {Function} [cfg.callback] Function to run with agent profile
10836        */
10837        getAgentProfile: function (key, cfg) {
10838            this.log("getAgentProfile");
10839            var queryCfg,
10840                lrs
10841            ;
10842
10843            if (this.recordStores.length > 0) {
10844                //
10845                // for agent profiles (for now) we are only going to store to the first LRS
10846                // so only get from there too
10847                //
10848                // TODO: make this the first non-allowFail LRS but for now it should
10849                // be good enough to make it the first since we know the LMS provided
10850                // LRS is the first
10851                //
10852                lrs = this.recordStores[0];
10853
10854                cfg = cfg || {};
10855
10856                queryCfg = {
10857                    agent: (typeof cfg.agent !== "undefined" ? cfg.agent : this.actor)
10858                };
10859                if (typeof cfg.callback !== "undefined") {
10860                    queryCfg.callback = cfg.callback;
10861                }
10862
10863                return lrs.retrieveAgentProfile(key, queryCfg);
10864            }
10865
10866            this.log("[warning] getAgentProfile: No LRSs added yet (agent profile not retrieved)");
10867        },
10868
10869        /**
10870        @method setAgentProfile
10871        @param {String}
10871 key Key to store into the agent profile
10872        @param {String|Object} val Value to store into the agent profile, objects will be stringified to JSON
10873        @param {Object} [cfg] Configuration for request
10874            @param {Object} [cfg.agent] Agent used in query,
10875                defaults to 'actor' property if empty
10876            @param {String} [cfg.lastSHA1] SHA1 of the previously seen existing profile
10877            @param {String} [cfg.contentType] Content-Type to specify in headers
10878            @param {Boolean} [cfg.overwriteJSON] If the Content-Type is JSON, should a PUT be used?
10879            @param {Function} [cfg.callback] Function to run with agent profile
10880        */
10881        setAgentProfile: function (key, val, cfg) {
10882            this.log("setAgentProfile");
10883            var queryCfg,
10884                lrs
10885            ;
10886
10887            if (this.recordStores.length > 0) {
10888                //
10889                // for agent profile (for now) we are only going to store to the first LRS
10890                // so only get from there too
10891                //
10892                // TODO: make this the first non-allowFail LRS but for now it should
10893                // be good enough to make it the first since we know the LMS provided
10894                // LRS is the first
10895                //
10896                lrs = this.recordStores[0];
10897
10898                cfg = cfg || {};
10899
10900                queryCfg = {
10901                    agent: (typeof cfg.agent !== "undefined" ? cfg.agent : this.actor)
10902                };
10903                if (typeof cfg.callback !== "undefined") {
10904                    queryCfg.callback = cfg.callback;
10905                }
10906                if (typeof cfg.lastSHA1 !== "undefined") {
10907                    queryCfg.lastSHA1 = cfg.lastSHA1;
10908                }
10909                if (typeof cfg.contentType !== "undefined") {
10910                    queryCfg.contentType = cfg.contentType;
10911                    if ((typeof cfg.overwriteJSON !== "undefined") && (! cfg.overwriteJSON) && (TinCan.Utils.isApplicationJSON(cfg.contentType))) {
10912                        queryCfg.method = "POST";
10913                    }
10914                }
10915
10916                return lrs.saveAgentProfile(key, val, queryCfg);
10917            }
10918
10919            this.log("[warning] setAgentProfile: No LRSs added yet (agent profile not saved)");
10920        },
10921
10922        /**
10923        @method deleteAgentProfile
10924        @param {String|null} key Key to remove from the agent profile, or null to clear all
10925        @param {Object} [cfg] Configuration for request
10926            @param {Object} [cfg.agent] Agent used in query,
10927                defaults to 'actor' property if empty
10928            @param {Function} [cfg.callback] Function to run with agent profile
10929        */
10930        deleteAgentProfile: function (key, cfg) {
10931            this.log("deleteAgentProfile");
10932            var queryCfg,
10933                lrs
10934            ;
10935
10936            if (this.recordStores.length > 0) {
10937                //
10938                // for agent profile (for now) we are only going to store to the first LRS
10939                // so only get from there too
10940                //
10941                // TODO: make this the first non-allowFail LRS but for now it should
10942                // be good enough to make it the first since we know the LMS provided
10943                // LRS is the first
10944                //
10945                lrs = this.recordStores[0];
10946
10947                cfg = cfg || {};
10948
10949                queryCfg = {
10950                    agent: (typeof cfg.agent !== "undefined" ? cfg.agent : this.actor)
10951                };
10952                if (typeof cfg.callback !== "undefined") {
10953                    queryCfg.callback = cfg.callback;
10954                }
10955
10956                return lrs.dropAgentProfile(key, queryCfg);
10957            }
10958
10959            this.log("[warning] deleteAgentProfile: No LRSs added yet (agent profile not deleted)");
10960        }
10961    };
10962
10963    /**
10964    @property DEBUG
10965    @static
10966    @default false
10967    */
10968    TinCan.DEBUG = false;
10969
10970    /**
10971    Turn on debug logging
10972
10973    @method enableDebug
10974    @static
10975    */
10976    TinCan.enableDebug = function () {
10977        TinCan.DEBUG = true;
10978    };
10979
10980    /**
10981    Turn off debug logging
10982
10983    @method disableDebug
10984    @static
10985    */
10986    TinCan.disableDebug = function () {
10987        TinCan.DEBUG = false;
10988    };
10989
10990    /**
10991    @method versions
10992    @return {Array} Array of supported version numbers
10993    @static
10994    */
10995    TinCan.versions = function () {
10996        // newest first so we can use the first as the default
10997        return [
10998            "1.0.2",
10999            "1.0.1",
11000            "1.0.0",
11001            "0.95",
11002            "0.9"
11003        ];
11004    };
11005
11006    /*global module*/
11007    // Support the CommonJS method for exporting our single global
11008    if (typeof module === "object") {
11009        module.exports = TinCan;
11010    }
11011}());
11012
11013/*
11014    Copyright 2012 Rustici Software
11015
11016    Licensed under the Apache License, Version 2.0 (the "License");
11017    you may not use this file except in compliance with the License.
11018    You may obtain a copy of the License at
11019
11020    http://www.apache.org/licenses/LICENSE-2.0
11021
11022    Unless required by applicable law or agreed to in writing, software
11023    distributed under the License is distributed on an "AS IS" BASIS,
11024    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11025    See the License for the specific language governing permissions and
11026    limitations under the License.
11027*/
11028
11029/**
11030TinCan client library
11031
11032@module TinCan
11033@submodule TinCan.Utils
11034**/
11035(function () {
11036    "use strict";
11037
11038    /**
11039    @class TinCan.Utils
11040    */
11041    TinCan.Utils = {
11042        defaultEncoding: "utf8",
11043
11044        /**
11045        Generates a UUIDv4 compliant string that should be reasonably unique
11046
11047        @method getUUID
11048        @return {String} UUID
11049        @static
11050
11051        Excerpt from: http://www.broofa.com/Tools/Math.uuid.js (v1.4)
11052        http://www.broofa.com
11053        mailto:[email protected]
11054        Copyright (c) 2010 Robert Kieffer
11055        Dual licensed under the MIT and GPL licenses.
11056        */
11057        getUUID: function () {
11058            /*jslint bitwise: true, eqeq: true */
11059            return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(
11060                /[xy]/g,
11061                function (c) {
11062                    var r = Math.random() * 16|0, v = c == "x" ? r : (r&0x3|0x8);
11063                    return v.toString(16);
11064                }
11065            );
11066        },
11067
11068        /**
11069        @method getISODateString
11070        @static
11071        @param {Date} date Date to stringify
11072        @return {String} ISO date String
11073        */
11074        getISODateString: function (d) {
11075            function pad (val, n) {
11076                var padder,
11077                    tempVal;
11078                if (typeof val === "undefined" || val === null) {
11079                    val = 0;
11080                }
11081                if (typeof n === "undefined" || n === null) {
11082                    n = 2;
11083                }
11084                padder = Math.pow(10, n-1);
11085                tempVal = val.toString();
11086
11087                while (val < padder && padder > 1) {
11088                    tempVal = "0" + tempVal;
11089                    padder = padder / 10;
11090                }
11091
11092                return tempVal;
11093            }
11094
11095            return d.getUTCFullYear() + "-" +
11096                pad(d.getUTCMonth() + 1) + "-" +
11097                pad(d.getUTCDate()) + "T" +
11098                pad(d.getUTCHours()) + ":" +
11099                pad(d.getUTCMinutes()) + ":" +
11100                pad(d.getUTCSeconds()) + "." +
11101                pad(d.getUTCMilliseconds(), 3) + "Z";
11102        },
11103
11104        /**
11105        @method convertISO8601DurationToMilliseconds
11106        @static
11107        @param {String} ISO8601Duration Duration in ISO8601 format
11108        @return {Int} Duration in milliseconds
11109
11110        Note: does not handle input strings with years, months and days
11111        */
11112        convertISO8601DurationToMilliseconds: function (ISO8601Duration) {
11113            var isValueNegative = (ISO8601Duration.indexOf("-") >= 0),
11114                indexOfT = ISO8601Duration.indexOf("T"),
11115                indexOfH = ISO8601Duration.indexOf("H"),
11116                indexOfM = ISO8601Duration.indexOf("M"),
11117                indexOfS = ISO8601Duration.indexOf("S"),
11118                hours,
11119                minutes,
11120                seconds,
11121                durationInMilliseconds;
11122
11123            if ((indexOfT === -1) || ((indexOfM !== -1) && (indexOfM < indexOfT)) || (ISO8601Duration.indexOf("D") !== -1) || (ISO8601Duration.indexOf("Y") !== -1)) {
11124                throw new Error("ISO 8601 timestamps including years, months and/or days are not currently supported");
11125            }
11126
11127            if (indexOfH === -1) {
11128                indexOfH = indexOfT;
11129                hours = 0;
11130            }
11131            else {
11132                hours = parseInt(ISO8601Duration.slice(indexOfT + 1, indexOfH), 10);
11133            }
11134
11135            if (indexOfM === -1) {
11136                indexOfM = indexOfT;
11137                minutes = 0;
11138            }
11139            else {
11140                minutes = parseInt(ISO8601Duration.slice(indexOfH + 1, indexOfM), 10);
11141            }
11142
11143            seconds = parseFloat(ISO8601Duration.slice(indexOfM + 1, indexOfS));
11144
11145            durationInMilliseconds = parseInt((((((hours * 60) + minutes) * 60) + seconds) * 1000), 10);
11146            if (isNaN(durationInMilliseconds)){
11147                durationInMilliseconds = 0;
11148            }
11149            if (isValueNegative) {
11150                durationInMilliseconds = durationInMilliseconds * -1;
11151            }
11152
11153            return durationInMilliseconds;
11154        },
11155
11156        /**
11157        @method convertMillisecondsToISO8601Duration
11158        @static
11159        @param {Int} inputMilliseconds Duration in milliseconds
11160        @return {String} Duration in ISO8601 format
11161        */
11162        convertMillisecondsToISO8601Duration: function (inputMilliseconds) {
11163            var hours,
11164                minutes,
11165                seconds,
11166                i_inputMilliseconds = parseInt(inputMilliseconds, 10),
11167                i_inputCentiseconds,
11168                inputIsNegative = "",
11169                rtnStr = "";
11170
11171            //round to nearest 0.01 seconds
11172            i_inputCentiseconds = Math.round(i_inputMilliseconds / 10);
11173
11174            if (i_inputCentiseconds < 0) {
11175                inputIsNegative = "-";
11176                i_inputCentiseconds = i_inputCentiseconds * -1;
11177            }
11178
11179            hours = parseInt(((i_inputCentiseconds) / 360000), 10);
11180            minutes = parseInt((((i_inputCentiseconds) % 360000) / 6000), 10);
11181            seconds = (((i_inputCentiseconds) % 360000) % 6000) / 100;
11182
11183            rtnStr = inputIsNegative + "PT";
11184            if (hours > 0) {
11185                rtnStr += hours + "H";
11186            }
11187
11188            if (minutes > 0) {
11189                rtnStr += minutes + "M";
11190            }
11191
11192            rtnStr += seconds + "S";
11193
11194            return rtnStr;
11195        },
11196
11197        /**
11198        @method getSHA1String
11199        @static
11200        @param {String} str Content to hash
11201        @return {String} SHA1 for contents
11202        */
11203        getSHA1String: function (str) {
11204            /*global CryptoJS*/
11205
11206            return CryptoJS.SHA1(str).toString(CryptoJS.enc.Hex);
11207        },
11208
11209        /**
11210        @method getSHA256String
11211        @static
11212        @param {ArrayBuffer|String} content Content to hash
11213        @return {String} SHA256 for contents
11214        */
11215        getSHA256String: function (content) {
11216            /*global CryptoJS*/
11217
11218            if (Object.prototype.toString.call(content) === "[object ArrayBuffer]") {
11219                content = CryptoJS.lib.WordArray.create(content);
11220            }
11221            return CryptoJS.SHA256(content).toString(CryptoJS.enc.Hex);
11222        },
11223
11224        /**
11225        @method getBase64String
11226        @static
11227        @param {String} str Content to encode
11228        @return {String} Base64 encoded contents
11229        */
11230        getBase64String: function (str) {
11231            /*global CryptoJS*/
11232
11233            return CryptoJS.enc.Base64.stringify(
11234                CryptoJS.enc.Latin1.parse(str)
11235            );
11236        },
11237
11238        /**
11239        Intended to be inherited by objects with properties that store
11240        display values in a language based "dictionary"
11241
11242        @method getLangDictionaryValue
11243        @param {String} prop Property name storing the dictionary
11244        @param {String} [lang] Language to return
11245        @return {String}
11246        */
11247        getLangDictionaryValue: function (prop, lang) {
11248            var langDict = this[prop],
11249                key;
11250
11251            if (typeof lang !== "undefined" && typeof langDict[lang] !== "undefined") {
11252                return langDict[lang];
11253            }
11254            if (typeof langDict.und !== "undefined") {
11255                return langDict.und;
11256            }
11257            if (typeof langDict["en-US"] !== "undefined") {
11258                return langDict["en-US"];
11259            }
11260            for (key in langDict) {
11261                if (langDict.hasOwnProperty(key)) {
11262                    return langDict[key];
11263                }
11264            }
11265
11266            return "";
11267        },
11268
11269        /**
11270        @method parseURL
11271        @param {String} url
11272        @param {Object} [options]
11273            @param {Boolean} [options.allowRelative] Option to allow relative URLs
11274        @return {Object} Object of values
11275        @private
11276        */
11277        parseURL: function (url, cfg) {
11278            //
11279            // see http://stackoverflow.com/a/21553982
11280            // and http://stackoverflow.com/a/2880929
11281            //
11282            var isRelative = url.charAt(0) === "/",
11283                _reURLInformation = [
11284                    "(/[^?#]*)", // pathname
11285                    "(\\?[^#]*|)", // search
11286                    "(#.*|)$" // hash
11287                ],
11288                reURLInformation,
11289                match,
11290                result,
11291                paramMatch,
11292                pl     = /\+/g,  // Regex for replacing addition symbol with a space
11293                search = /([^&=]+)=?([^&]*)/g,
11294                decode = function (s) { return decodeURIComponent(s.replace(pl, " ")); };
11295
11296            cfg = cfg || {};
11297
11298            //
11299            // this method in an earlier version supported relative URLs, mostly to provide
11300            // support to the `LRS.moreStatements` method, that functionality was removed and
11301            // subsequently restored but with the addition of the option for allowing relative
11302            // URLs to be accepted which is the reason for the "helpful" exception message here
11303            //
11304            if (! isRelative) {
11305                //
11306                // not relative so make sure they have a scheme, host, etc.
11307                //
11308                _reURLInformation.unshift(
11309                    "^(https?:)//", // scheme
11310                    "(([^:/?#]*)(?::([0-9]+))?)" // host (hostname and port)
11311                );
11312
11313                //
11314                // our regex requires there to be a '/' for the detection of the start
11315                // of the path, we can detect a '/' using indexOf beyond the part of the
11316                // scheme, since we've restricted scheme to 'http' or 'https' and because
11317                // a hostname is guaranteed to be there we can detect beyond the '://'
11318                // based on position, then tack on a trailing '/' because it can't be
11319                // part of the path
11320                //
11321                if (url.indexOf("/", 8) === -1) {
11322                    url = url + "/";
11323                }
11324            }
11325            else {
11326                //
11327                // relative so make sure they allow that explicitly
11328                //
11329                if (typeof cfg.allowRelative === "undefined" || ! cfg.allowRelative) {
11330                    throw new Error("Refusing to parse relative URL without 'allowRelative' option");
11331                }
11332            }
11333
11334            reURLInformation = new RegExp(_reURLInformation.join(""));
11335            match = url.match(reURLInformation);
11336            if (match === null) {
11337                throw new Error("Unable to parse URL regular expression did not match: '" + url + "'");
11338            }
11339
11340            // 'path' is for backwards compatibility
11341            if (isRelative) {
11342                result = {
11343                    protocol: null,
11344                    host: null,
11345                    hostname: null,
11346                    port: null,
11347                    path: null,
11348                    pathname: match[1],
11349                    search: match[2],
11350                    hash: match[3],
11351                    params: {}
11352                };
11353
11354                result.path = result.pathname;
11355            }
11356            else {
11357                result = {
11358                    protocol: match[1],
11359                    host: match[2],
11360                    hostname: match[3],
11361                    port: match[4],
11362                    pathname: match[5],
11363                    search: match[6],
11364                    hash: match[7],
11365                    params: {}
11366                };
11367
11368                result.path = result.protocol + "//" + result.host 
11368+ result.pathname;
11369            }
11370
11371            if (result.search !== "") {
11372                // extra parens to let jshint know this is an expression
11373                while ((paramMatch = search.exec(result.search.substring(1)))) {
11374                    result.params[decode(paramMatch[1])] = decode(paramMatch[2]);
11375                }
11376            }
11377
11378            return result;
11379        },
11380
11381        /**
11382        @method getServerRoot
11383        @param {String} absoluteUrl
11384        @return {String} server root of url
11385        @private
11386        */
11387        getServerRoot: function (absoluteUrl) {
11388            var urlParts = absoluteUrl.split("/");
11389            return urlParts[0] + "//" + urlParts[2];
11390        },
11391
11392        /**
11393        @method getContentTypeFromHeader
11394        @static
11395        @param {String} header Content-Type header value
11396        @return {String} Primary value from Content-Type
11397        */
11398        getContentTypeFromHeader: function (header) {
11399            return (String(header).split(";"))[0];
11400        },
11401
11402        /**
11403        @method isApplicationJSON
11404        @static
11405        @param {String} header Content-Type header value
11406        @return {Boolean} whether "application/json" was matched
11407        */
11408        isApplicationJSON: function (header) {
11409            return TinCan.Utils.getContentTypeFromHeader(header).toLowerCase().indexOf("application/json") === 0;
11410        },
11411
11412        /**
11413        @method stringToArrayBuffer
11414        @static
11415        @param {String} content String of content to convert to an ArrayBuffer
11416        @param {String} [encoding] Encoding to use for conversion
11417        @return {ArrayBuffer} Converted content
11418        */
11419        stringToArrayBuffer: function () {
11420            TinCan.prototype.log("stringToArrayBuffer not overloaded - no environment loaded?");
11421        },
11422
11423        /**
11424        @method stringFromArrayBuffer
11425        @static
11426        @param {ArrayBuffer} content ArrayBuffer of content to convert to a String
11427        @param {String} [encoding] Encoding to use for conversion
11428        @return {String} Converted content
11429        */
11430        stringFromArrayBuffer: function () {
11431            TinCan.prototype.log("stringFromArrayBuffer not overloaded - no environment loaded?");
11432        }
11433    };
11434}());
11435
11436/*
11437    Copyright 2012-2013 Rustici Software
11438
11439    Licensed under the Apache License, Version 2.0 (the "License");
11440    you may not use this file except in compliance with the License.
11441    You may obtain a copy of the License at
11442
11443    http://www.apache.org/licenses/LICENSE-2.0
11444
11445    Unless required by applicable law or agreed to in writing, software
11446    distributed under the License is distributed on an "AS IS" BASIS,
11447    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11448    See the License for the specific language governing permissions and
11449    limitations under the License.
11450*/
11451
11452/**
11453TinCan client library
11454
11455@module TinCan
11456@submodule TinCan.LRS
11457**/
11458(function () {
11459    "use strict";
11460    /**
11461    @class TinCan.LRS
11462    @constructor
11463    */
11464    var LRS = TinCan.LRS = function (cfg) {
11465        this.log("constructor");
11466
11467        /**
11468        @property endpoint
11469        @type String
11470        */
11471        this.endpoint = null;
11472
11473        /**
11474        @property version
11475        @type String
11476        */
11477        this.version = null;
11478
11479        /**
11480        @property auth
11481        @type String
11482        */
11483        this.auth = null;
11484
11485        /**
11486        @property allowFail
11487        @type Boolean
11488        @default true
11489        */
11490        this.allowFail = true;
11491
11492        /**
11493        @property extended
11494        @type Object
11495        */
11496        this.extended = null;
11497
11498        this.init(cfg);
11499    };
11500    LRS.prototype = {
11501        /**
11502        @property LOG_SRC
11503        */
11504        LOG_SRC: "LRS",
11505
11506        /**
11507        @method log
11508        */
11509        log: TinCan.prototype.log,
11510
11511        /**
11512        @method init
11513        */
11514        init: function (cfg) {
11515            this.log("init");
11516
11517            var versions = TinCan.versions(),
11518                versionMatch = false,
11519                i
11520            ;
11521
11522            cfg = cfg || {};
11523
11524            if (cfg.hasOwnProperty("alertOnRequestFailure")) {
11525                this.log("'alertOnRequestFailure' is deprecated (alerts have been removed) no need to set it now");
11526            }
11527
11528            if (! cfg.hasOwnProperty("endpoint") || cfg.endpoint === null || cfg.endpoint === "") {
11529                this.log("[error] LRS invalid: no endpoint");
11530                throw {
11531                    code: 3,
11532                    mesg: "LRS invalid: no endpoint"
11533                };
11534            }
11535
11536            this.endpoint = String(cfg.endpoint);
11537            if (this.endpoint.slice(-1) !== "/") {
11538                this.log("adding trailing slash to endpoint");
11539                this.endpoint += "/";
11540            }
11541
11542            if (cfg.hasOwnProperty("allowFail")) {
11543                this.allowFail = cfg.allowFail;
11544            }
11545
11546            if (cfg.hasOwnProperty("auth")) {
11547                this.auth = cfg.auth;
11548            }
11549            else if (cfg.hasOwnProperty("username") && cfg.hasOwnProperty("password")) {
11550                this.auth = "Basic " + TinCan.Utils.getBase64String(cfg.username + ":" + cfg.password);
11551            }
11552
11553            if (cfg.hasOwnProperty("extended")) {
11554                this.extended = cfg.extended;
11555            }
11556
11557            //
11558            // provide a hook method that environments can override
11559            // to handle anything necessary in the initialization
11560            // process that is customized to them, such as cross domain
11561            // setup in browsers, default implementation is empty
11562            //
11563            // this hook must run prior to version detection so that
11564            // request handling can be set up before requesting the
11565            // LRS version via the /about resource
11566            //
11567            this._initByEnvironment(cfg);
11568
11569            if (typeof cfg.version !== "undefined") {
11570                this.log("version: " + cfg.version);
11571                for (i = 0; i < versions.length; i += 1) {
11572                    if (versions[i] === cfg.version) {
11573                        versionMatch = true;
11574                        break;
11575                    }
11576                }
11577                if (! versionMatch) {
11578                    this.log("[error] LRS invalid: version not supported (" + cfg.version + ")");
11579                    throw {
11580                        code: 5,
11581                        mesg: "LRS invalid: version not supported (" + cfg.version + ")"
11582                    };
11583                }
11584                this.version = cfg.version;
11585            }
11586            else {
11587                //
11588                // assume max supported when not specified,
11589                // TODO: add detection of LRS from call to endpoint
11590                //
11591                this.version = versions[0];
11592            }
11593        },
11594
11595        /**
11596        Creates and returns a boundary for separating parts in
11597        requests where the statement has an attachment
11598
11599        @method _getBoundary
11600        @private
11601        */
11602        _getBoundary: function () {
11603            return TinCan.Utils.getUUID().replace(/-/g, "");
11604        },
11605
11606        /**
11607        Method should be overloaded by an environment to do per
11608        environment specifics such that the LRS can make a call
11609        to set the version if not provided
11610
11611        @method _initByEnvironment
11612        @private
11613        */
11614        _initByEnvironment: function () {
11615            this.log("_initByEnvironment not overloaded - no environment loaded?");
11616        },
11617
11618        /**
11619        Method should be overloaded by an environment to do per
11620        environment specifics for sending requests to the LRS
11621
11622        @method _makeRequest
11623        @private
11624        */
11625        _makeRequest: function () {
11626            this.log("_makeRequest not overloaded - no environment loaded?");
11627        },
11628
11629        /**
11630        Method should be overloaded by an environment to do per
11631        environment specifics for building multipart request data
11632
11633        @method _getMultipartRequestData
11634        @private
11635        */
11636        _getMultipartRequestData: function () {
11637            this.log("_getMultipartRequestData not overloaded - no environment loaded?");
11638        },
11639
11640        /**
11641        Method is overloaded by the browser environment in order to test c
11641onverting an
11642        HTTP request that is greater than a defined length
11643
11644        @method _IEModeConversion
11645        @private
11646        */
11647        _IEModeConversion: function () {
11648            this.log("_IEModeConversion not overloaded - browser environment not loaded.");
11649        },
11650
11651        _processGetStatementResult: function (xhr, params) {
11652            var boundary,
11653                parsedResponse,
11654                statement,
11655                attachmentMap = {},
11656                i;
11657
11658            if (! params.attachments) {
11659                return TinCan.Statement.fromJSON(xhr.responseText);
11660            }
11661
11662            boundary = xhr.getResponseHeader("Content-Type").split("boundary=")[1];
11663
11664            parsedResponse = this._parseMultipart(boundary, xhr.response);
11665            statement = JSON.parse(parsedResponse[0].body);
11666            for (i = 1; i < parsedResponse.length; i += 1) {
11667                attachmentMap[parsedResponse[i].headers["X-Experience-API-Hash"]] = parsedResponse[i].body;
11668            }
11669
11670            this._assignAttachmentContent([statement], attachmentMap);
11671
11672            return new TinCan.Statement(statement);
11673        },
11674
11675        /**
11676        Method used to send a request via browser objects to the LRS
11677
11678        @method sendRequest
11679        @param {Object} cfg Configuration for request
11680            @param {String} cfg.url URL portion to add to endpoint
11681            @param {String} [cfg.method] GET, PUT, POST, etc.
11682            @param {Object} [cfg.params] Parameters to set on the querystring
11683            @param {String|ArrayBuffer} [cfg.data] Body content as a String or ArrayBuffer
11684            @param {Object} [cfg.headers] Additional headers to set in the request
11685            @param {Function} [cfg.callback] Function to run at completion
11686                @param {String|Null} cfg.callback.err If an error occurred, this parameter will contain the HTTP status code.
11687                    If the operation succeeded, err will be null.
11688                @param {Object} cfg.callback.xhr XHR object
11689            @param {Boolean} [cfg.ignore404] Whether 404 status codes should be considered an error
11690            @param {Boolean} [cfg.expectMultipart] Whether to expect the response to be a multipart response
11691        @return {Object} XHR if called in a synchronous way (in other words no callback)
11692        */
11693        sendRequest: function (cfg) {
11694            this.log("sendRequest");
11695            var fullUrl = this.endpoint + cfg.url,
11696                headers = {},
11697                prop
11698            ;
11699
11700            // respect absolute URLs passed in
11701            if (cfg.url.indexOf("http") === 0) {
11702                fullUrl = cfg.url;
11703            }
11704
11705            // add extended LMS-specified values to the params
11706            if (this.extended !== null) {
11707                cfg.params = cfg.params || {};
11708
11709                for (prop in this.extended) {
11710                    if (this.extended.hasOwnProperty(prop)) {
11711                        // don't overwrite cfg.params values that have already been added to the request with our extended params
11712                        if (! cfg.params.hasOwnProperty(prop)) {
11713                            if (this.extended[prop] !== null) {
11714                                cfg.params[prop] = this.extended[prop];
11715                            }
11716                        }
11717                    }
11718                }
11719            }
11720
11721            // consolidate headers
11722            headers.Authorization = this.auth;
11723            if (this.version !== "0.9") {
11724                headers["X-Experience-API-Version"] = this.version;
11725            }
11726
11727            for (prop in cfg.headers) {
11728                if (cfg.headers.hasOwnProperty(prop)) {
11729                    headers[prop] = cfg.headers[prop];
11730                }
11731            }
11732
11733            return this._makeRequest(fullUrl, headers, cfg);
11734        },
11735
11736        /**
11737        Method used to determine the LRS version
11738
11739        @method about
11740        @param {Object} cfg Configuration object for the about request
11741            @param {Function} [cfg.callback] Callback to execute upon receiving a response
11742            @param {Object} [cfg.params] this is needed, but can be empty
11743        @return {Object} About which holds the version, or asyncrhonously calls a specified callback
11744        */
11745        about: function (cfg) {
11746            this.log("about");
11747            var requestCfg,
11748                requestResult,
11749                callbackWrapper;
11750
11751            cfg = cfg || {};
11752
11753            requestCfg = {
11754                url: "about",
11755                method: "GET",
11756                params: {}
11757            };
11758            if (typeof cfg.callback !== "undefined") {
11759                callbackWrapper = function (err, xhr) {
11760                    var result = xhr;
11761
11762                    if (err === null) {
11763                        result = TinCan.About.fromJSON(xhr.responseText);
11764                    }
11765
11766                    cfg.callback(err, result);
11767                };
11768                requestCfg.callback = callbackWrapper;
11769            }
11770
11771            requestResult = this.sendRequest(requestCfg);
11772
11773            if (callbackWrapper) {
11774                return;
11775            }
11776
11777            if (requestResult.err === null) {
11778                requestResult.xhr = TinCan.About.fromJSON(requestResult.xhr.responseText);
11779            }
11780            return requestResult;
11781        },
11782
11783        /**
11784        Save a statement, when used from a browser sends to the endpoint using the RESTful interface.
11785        Use a callback to make the call asynchronous.
11786
11787        @method saveStatement
11788        @param {TinCan.Statement} statement to send
11789        @param {Object} [cfg] Configuration used when saving
11790            @param {Function} [cfg.callback] Callback to execute on completion
11791        */
11792        saveStatement: function (stmt, cfg) {
11793            this.log("saveStatement");
11794            var requestCfg = {
11795                    url: "statements",
11796                    headers: {}
11797                },
11798                versionedStatement,
11799                requestAttachments = [],
11800                boundary,
11801                i;
11802
11803            cfg = cfg || {};
11804
11805            try {
11806                versionedStatement = stmt.asVersion( this.version );
11807            }
11808            catch (ex) {
11809                if (this.allowFail) {
11810                    this.log("[warning] statement could not be serialized in version (" + this.version + "): " + ex);
11811                    if (typeof cfg.callback !== "undefined") {
11812                        cfg.callback(null, null);
11813                        return;
11814                    }
11815                    return {
11816                        err: null,
11817                        xhr: null
11818                    };
11819                }
11820
11821                this.log("[error] statement could not be serialized in version (" + this.version + "): " + ex);
11822                if (typeof cfg.callback !== "undefined") {
11823                    cfg.callback(ex, null);
11824                    return;
11825                }
11826                return {
11827                    err: ex,
11828                    xhr: null
11829                };
11830            }
11831
11832            if (versionedStatement.hasOwnProperty("attachments") && stmt.hasAttachmentWithContent()) {
11833                boundary = this._getBoundary();
11834
11835                requestCfg.headers["Content-Type"] = "multipart/mixed; boundary=" + boundary;
11836
11837                for (i = 0; i < stmt.attachments.length; i += 1) {
11838                    if (stmt.attachments[i].content !== null) {
11839                        requestAttachments.push(stmt.attachments[i]);
11840                    }
11841                }
11842
11843                try {
11844                    requestCfg.data = this._getMultipartRequestData(boundary, versionedStatement, requestAttachments);
11845                }
11846                catch (ex) {
11847                    if (this.allowFail) {
11848                        this.log("[warning] multipart request data could not be created (attachments probably not supported): " + ex);
11849                        if (typeof cfg.callback !== "undefined") {
11850                            cfg.callback(null, null);
11851                            return;
11852                        }
11853                        return {
11854                            err: null,
11855                            xhr: null
11856                        };
11857                    }
11858
11859                    this.log("[error] multipart request data could not be created (attachments probably not supported): " + ex);
11860                    if (typeof cfg.callback !== "undefined") {
11861                        cfg.callback(ex, null);
11862                        return;
11863                    }
11864                    return {
11865                        err: ex,
11866                        xhr: null
11867                    };
11868                }
11869            }
11870            else {
11871                requestCfg.headers["Content-Type"] = "application/json";
11872                requestCfg.data = JSON.stringify(versionedStatement);
11873            }
11874            if (stmt.id !== null) {
11875                requestCfg.method = "PUT";
11876                requestCfg.params = {
11877                    statementId: stmt.id
11878                };
11879            }
11880            else {
11881                requestCfg.method = "POST";
11882            }
11883
11884            if (typeof cfg.callback !== "undefined") {
11885                requestCfg.callback = cfg.callback;
11886            }
11887
11888            return this.sendRequest(requestCfg);
11889        },
11890
11891        /**
11892        Retrieve a statement, when used from a browser sends to the endpoint using the RESTful interface.
11893
11894        @method retrieveStatement
11895        @param {String} ID of statement to retrieve
11896        @param {Object} [cfg] Configuration options
11897            @param {Object} [cfg.params] Query parameters
11898                @param {Boolean} [cfg.params.attachments] Include attachments in multipart response or don't (default: false)
11899            @param {Function} [cfg.callback] Callback to execute on completion
11900        @return {TinCan.Statement} Statement retrieved
11901        */
11902        retrieveStatement: function (stmtId, cfg) {
11903            this.log("retrieveStatement");
11904            var requestCfg,
11905                requestResult,
11906                callbackWrapper,
11907                lrs = this;
11908
11909            cfg = cfg || {};
11910            cfg.params = cfg.params || {};
11911
11912            requestCfg = {
11913                url: "statements",
11914                method: "GET",
11915                params: {
11916                    statementId: stmtId
11917                }
11918            };
11919            if (cfg.params.attachments) {
11920                requestCfg.params.attachments = true;
11921                requestCfg.expectMultipart = true;
11922            }
11923            if (typeof cfg.callback !== "undefined") {
11924                callbackWrapper = function (err, xhr) {
11925                    var result = xhr;
11926
11927                    if (err === null) {
11928                        result = lrs._processGetStatementResult(xhr, cfg.params);
11929                    }
11930
11931                    cfg.callback(err, result);
11932                };
11933                requestCfg.callback = callbackWrapper;
11934            }
11935
11936            requestResult = this.sendRequest(requestCfg);
11937            if (! callbackWrapper) {
11938                requestResult.statement = null;
11939                if (requestResult.err === null) {
11940                    requestResult.statement = lrs._processGetStatementResult(requestResult.xhr, cfg.params);
11941                }
11942            }
11943
11944            return requestResult;
11945        },
11946
11947        /**
11948        Retrieve a voided statement, when used from a browser sends to the endpoint using the RESTful interface.
11949
11950        @method retrieveVoidedStatement
11951        @param {String} ID of voided statement to retrieve
11952        @param {Object} [cfg] Configuration options
11953            @param {Object} [cfg.params] Query parameters
11954                @param {Boolean} [cfg.params.attachments] Include attachments in multipart response or don't (default: false)
11955            @param {Function} [cfg.callback] Callback to execute on completion
11956        @return {TinCan.Statement} Statement retrieved
11957        */
11958        retrieveVoidedStatement: function (stmtId, cfg) {
11959            this.log("retrieveVoidedStatement");
11960            var requestCfg,
11961                requestResult,
11962                callbackWrapper,
11963                lrs = this;
11964
11965            cfg = cfg || {};
11966            cfg.params = cfg.params || {};
11967
11968            requestCfg = {
11969                url: "statements",
11970                method: "GET",
11971                params: {}
11972            };
11973            if (this.version === "0.9" || this.version === "0.95") {
11974                requestCfg.params.statementId = stmtId;
11975            }
11976            else {
11977                requestCfg.params.voidedStatementId = stmtId;
11978                if (cfg.params.attachments) {
11979                    requestCfg.params.attachments = true;
11980                    requestCfg.expectMultipart = true;
11981                }
11982            }
11983
11984            if (typeof cfg.callback !== "undefined") {
11985                callbackWrapper = function (err, xhr) {
11986                    var result = xhr;
11987
11988                    if (err === null) {
11989                        result = lrs._processGetStatementResult(xhr, cfg.params);
11990                    }
11991
11992                    cfg.callback(err, result);
11993                };
11994                requestCfg.callback = callbackWrapper;
11995            }
11996
11997            requestResult = this.sendRequest(requestCfg);
11998            if (! callbackWrapper) {
11999                requestResult.statement = null;
12000                if (requestResult.err === null) {
12001                    requestResult.statement = lrs._processGetStatementResult(requestResult.xhr, cfg.params);
12002                }
12003            }
12004
12005            return requestResult;
12006        },
12007
12008        /**
12009        Save a set of statements, when used from a browser sends to the endpoint using the RESTful interface.
12010        Use a callback to make the call asynchronous.
12011
12012        @method saveStatements
12013        @param {Array} Array of statements or objects convertable to statements
12014        @param {Object} [cfg] Configuration used when saving
12015            @param {Function} [cfg.callback] Callback to execute on completion
12016        */
12017        saveStatements: function (stmts, cfg) {
12018            this.log("saveStatements");
12019            var requestCfg = {
12020                    url: "statements",
12021                    method: "POST",
12022                    headers: {}
12023                },
12024                versionedStatement,
12025                versionedStatements = [],
12026                requestAttachments = [],
12027                boundary,
12028                i,
12029                j;
12030
12031            cfg = cfg || {};
12032
12033            if (stmts.length === 0) {
12034                if (typeof cfg.callback !== "undefined") {
12035                    cfg.callback(new Error("no statements"), null);
12036                    return;
12037                }
12038                return {
12039                    err: new Error("no statements"),
12040                    xhr: null
12041                };
12042            }
12043
12044            for (i = 0; i < stmts.length; i += 1) {
12045                try {
12046                    versionedStatement = stmts[i].asVersion( this.version );
12047                }
12048                catch (ex) {
12049                    if (this.allowFail) {
12050                        this.log("[warning] statement could not be serialized in version (" + this.version + "): " + ex);
12051                        if (typeof cfg.callback !== "undefined") {
12052                            cfg.callback(null, null);
12053                            return;
12054                        }
12055                        return {
12056                            err: null,
12057                            xhr: null
12058                        };
12059                    }
12060
12061                    this.log("[error] statement could not be serialized in version (" + this.version + "): " + ex);
12062                    if (typeof cfg.callback !== "undefined") {
12063                        cfg.callback(ex, null);
12064                        return;
12065                    }
12066                    return {
12067                        err: ex,
12068                        xhr: null
12069                    };
12070                }
12071
12072                if (stmts[i].hasAttachmentWithContent()) {
12073                    for (j = 0; j < stmts[i].attachments.length; j += 1) {
12074                        if (stmts[i].attachments[j].content !== null) {
12075                            requestAttachments.push(stmts[i].attachments[j]);
12076                        }
12077                    }
12078                }
12079
12080                versionedStatements.push(versionedStatement);
12081            }
12082
12083            if (requestAttachments.length !== 0) {
12084                boundary = this._getBoundary();
12085
12086                requestCfg.headers["Content-Type"] = "multipart/mixed; boundary=" + boundary;
12087
12088                try {
12089                    requestCfg.data = this._getMultipartRequestData(boundary, versionedStatements, requestAttachments);
12090                }
12091                catch (ex) {
12092                    if (this.allowFail) {
12093                        this.log("[warning] multipart request data could not be created (attachments probably not supported): " + ex);
12094                        if (typeof cfg.callback !== "undefined") {
12095                            cfg.callback(null, null);
12096                            return;
12097                        }
12098                        return {
12099                            err: null,
12100                            xhr: null
12101                        };
12102                    }
12103
12104                    this.log("[error] multipart request data could not be created (attachments probably not supported): " + ex);
12105                    if (typeof cfg.callback !== "undefined") {
12106                        cfg.callback(ex, null);
12107                        return;
12108                    }
12109                    return {
12110                        err: ex,
12111                        xhr: null
12112                    };
12113                }
12114            }
12115            else {
12116                requestCfg.headers["Content-Type"] = "application/json";
12117                requestCfg.data = JSON.stringify(versionedStatements);
12118            }
12119
12120            if (typeof cfg.callback !== "undefined") {
12121                requestCfg.callback = cfg.callback;
12122            }
12123
12124            return this.sendRequest(requestCfg);
12125        },
12126
12127        /**
12128        Fetch a set of statements, when used from a browser sends to the endpoint using the
12129        RESTful interface.  Use a callback to make the call asynchronous.
12130
12131        @method queryStatements
12132        @param {Object} [cfg] Configuration used to query
12133            @param {Object} [cfg.params] Query parameters
12134                @param {TinCan.Agent|TinCan.Group} [cfg.params.agent] Agent matches 'actor' or 'object'
12135                @param {TinCan.Verb|String} [cfg.params.verb] Verb (or verb ID) to query on
12136                @param {TinCan.Activity|String} [cfg.params.activity] Activity (or activity ID) to query on
12137                @param {String} [cfg.params.registration] Registration UUID
12138                @param {Boolean} [cfg.params.related_activities] Match related activities
12139                @param {Boolean} [cfg.params.related_agents] Match related agents
12140                @param {String} [cfg.params.since] Match statements stored since specified timestamp
12141                @param {String} [cfg.params.until] Match statements stored at or before specified timestamp
12142                @param {Integer} [cfg.params.limit] Number of results to retrieve
12143                @param {String} [cfg.params.format] One of "ids", "exact", "canonical" (default: "exact")
12144                @param {Boolean} [cfg.params.ascending] Return results in ascending order of stored time
12145
12146                @param {TinCan.Agent} [cfg.params.actor] (Removed in 1.0.0, use 'agent' instead) Agent matches 'actor'
12147                @param {TinCan.Activity|TinCan.Agent|TinCan.Statement} [cfg.params.target] (Removed in 1.0.0, use 'activity' or 'agent' instead) Activity, Agent, or Statement matches 'object'
12148                @param {TinCan.Agent} [cfg.params.instructor] (Removed in 1.0.0, use 'agent' + 'related_agents' instead) Agent matches 'context:instructor'
12149                @param {Boolean} [cfg.params.context] (Removed in 1.0.0, use 'activity' instead) When filtering on target, include statements with matching context
12150                @param {Boolean} [cfg.params.authoritative] (Removed in 1.0.0) Get authoritative results
12151                @param {Boolean} [cfg.params.sparse] (Removed in 1.0.0, use 'format' instead) Get sparse results
12152
12153            @param {Function} [cfg.callback] Callback to execute on completion
12154                @param {String|null} cfg.callback.err Error status or null if succcess
12155                @param {TinCan.StatementsResult|XHR} cfg.callback.response Receives a StatementsResult argument
12156        @return {Object} Request result
12157        */
12158        queryStatements: function (cfg) {
12159            this.log("queryStatements");
12160            var requestCfg,
12161                requestResult,
12162                callbackWrapper,
12163                lrs = this;
12164
12165            cfg = cfg || {};
12166            cfg.params = cfg.params || {};
12167
12168            //
12169            // if they misconfigured (possibly due to version mismatches) the
12170            // query then don't try to send a request at all, rather than give
12171            // them invalid results
12172            //
12173            try {
12174                requestCfg = this._queryStatementsRequestCfg(cfg);
12175
12176                if (cfg.params.attachments) {
12177                    requestCfg.expectMultipart = true;
12178                }
12179            }
12180            catch (ex) {
12181                this.log("[error] Query statements failed - " + ex);
12182                if (typeof cfg.callback !== "undefined") {
12183                    cfg.callback(ex, {});
12184                }
12185
12186                return {
12187                    err: ex,
12188                    statementsResult: null
12189                };
12190            }
12191
12192            if (typeof cfg.callback !== "undefined") {
12193                callbackWrapper = function (err, xhr) {
12194                    var result = xhr,
12195                        parsedResponse,
12196                        boundary,
12197                        statements,
12198                        attachmentMap = {},
12199                        i;
12200
12201                    if (err === null) {
12202                        if (! cfg.params.attachments) {
12203                            result = TinCan.StatementsResult.fromJSON(xhr.responseText);
12204                        }
12205                        else {
12206                            boundary = xhr.getResponseHeader("Content-Type").split("boundary=")[1];
12207
12208                            parsedResponse = lrs._parseMultipart(boundary, xhr.response);
12209                            statements = JSON.parse(parsedResponse[0].body);
12210                            for (i = 1; i < parsedResponse.length; i += 1) {
12211                                attachmentMap[parsedResponse[i].headers["X-Experience-API-Hash"]] = parsedResponse[i].body;
12212                            }
12213
12214                            lrs._assignAttachmentContent(statements.statements, attachmentMap);
12215                            result = new TinCan.StatementsResult({ statements: statements.statements });
12216
12217                            for (i = 0; i < result.statements.length; i += 1) {
12218                                if (! (result.statements[i] instanceof TinCan.Statement)) {
12219                                    result.statements[i] = new TinCan.Statement(result.statements[i]);
12220                                }
12221                            }
12222                        }
12223                    }
12224
12225                    cfg.callback(err, result);
12226                };
12227                requestCfg.callback = callbackWrapper;
12228            }
12229
12230            requestResult = this.sendRequest(requestCfg);
12231            requestResult.config = requestCfg;
12232
12233            if (! callbackWrapper) {
12234                requestResult.statementsResult = null;
12235                if (requestResult.err === null) {
12236                    requestResult.statementsResult = TinCan.StatementsResult.fromJSON(requestResult.xhr.responseText);
12237                }
12238            }
12239
12240            return requestResult;
12241        },
12242
12243        /**
12244        Build a request config object that can be passed to sendRequest() to make a query request
12245
12246        @method _queryStatementsRequestCfg
12247        @private
12248        @param {Object} [cfg] See configuration for {{#crossLink "TinCan.LRS/queryStatements"}}{{/crossLink}}
12249        @return {Object} Request configuration object
12250        */
12251        _queryStatementsRequestCfg: function (cfg) {
12252            this.log("_queryStatementsRequestCfg");
12253            var params = {},
12254                returnCfg = {
12255                    url: "statements",
12256                    method: "GET",
12257                    params: params
12258                },
12259                jsonProps = [
12260                    "agent",
12261                    "actor",
12262                    "object",
12263                    "instructor"
12264                ],
12265                idProps = [
12266                    "verb",
12267                    "activity"
12268                ],
12269                valProps = [
12270                    "registration",
12271                    "context",
12272                    "since",
12273                    "until",
12274                    "limit",
12275                    "authoritative",
12276                    "sparse",
12277                    "ascending",
12278                    "related_activities",
12279                    "related_agents",
12280                    "format",
12281                    "attachments"
12282                ],
12283                i,
12284                prop,
12285                //
12286                // list of parameters that are supported in all versions (supported by
12287                // this library) of the spec
12288                //
12289                universal = {
12290                    verb: true,
12291                    registration: true,
12292                    since: true,
12293                    until: true,
12294                    limit: true,
12295                    ascending: true
12296                },
12297                //
12298                // future proofing here, "supported" is an object so that
12299                // in the future we can support a "deprecated" list to
12300                // throw warnings, hopefully the spec uses deprecation phases
12301                // for the removal of these things
12302                //
12303                compatibility = {
12304                    "0.9": {
12305                        supported: {
12306                            actor: true,
12307                            instructor: true,
12308                            target: true,
12309                            object: true,
12310                            context: true,
12311                            authoritative: true,
12312                            sparse: true
12313                        }
12314                    },
12315                    "1.0.0": {
12316                        supported: {
12317                            agent: true,
12318                            activity: true,
12319                            related_activities: true,
12320                            related_agents: true,
12321                            format: true,
12322                            attachments: true
12323                        }
12324                    }
12325                };
12326
12327            compatibility["0.95"] = compatibility["0.9"];
12328            compatibility["1.0.1"] = compatibility["1.0.0"];
12329            compatibility["1.0.2"] = compatibility["1.0.0"];
12330
12331            if (cfg.params.hasOwnProperty("target")) {
12332                cfg.params.object = cfg.params.target;
12333            }
12334
12335            //
12336            // check compatibility tables, either the configured parameter is in
12337            // the universal list or the specific version, if not then throw an
12338            // error which at least for .queryStatements will prevent the request
12339            // and potentially alert the user
12340            //
12341            for (prop in cfg.params) {
12342                if (cfg.params.hasOwnProperty(prop)) {
12343                    if (typeof universal[prop] === "undefined" && typeof compatibility[this.version].supported[prop] === "undefined") {
12344                        throw "Unrecognized query parameter configured: " + prop;
12345                    }
12346                }
12347            }
12348
12349            //
12350            // getting here means that all parameters are valid for this version
12351            // to make handling the output formats easier
12352            //
12353
12354            for (i = 0; i < jsonProps.length; i += 1) {
12355                if (typeof cfg.params[jsonProps[i]] !== "undefined") {
12356                    params[jsonProps[i]] = JSON.stringify(cfg.params[jsonProps[i]].asVersion(this.version));
12357                }
12358            }
12359
12360            for (i = 0; i < idProps.length; i += 1) {
12361                if (typeof cfg.params[idProps[i]] !== "undefined") {
12362                    if (typeof cfg.params[idProps[i]].id === "undefined") {
12363                        params[idProps[i]] = cfg.params[idProps[i]];
12364                    }
12365                    else {
12366                        params[idProps[i]] = cfg.params[idProps[i]].id;
12367                    }
12368                }
12369            }
12370
12371            for (i = 0; i < valProps.length; i += 1) {
12372                if (typeof cfg.params[valProps[i]] !== "undefined" && cfg.params[valProps[i]] !== null) {
12373                    params[valProps[i]] = cfg.params[valProps[i]];
12374                }
12375            }
12376
12377            return returnCfg;
12378        },
12379
12380        /**
12381        Assigns attachment content to the correct attachment to create a StatementsResult object that is sent
12382        to the callback of queryStatements()
12383
12384        @method _assignAttachmentContent
12385        @private
12386        @param {Array} [stmts] Array of TinCan.Statement JSON objects
12387        @param {Object} [attachmentMap] Map of the content to place into its attachment
12388        @return {Array} Array of TinCan.Statement JSON objects with correctly assigned attachment content
12389        */
12390        _assignAttachmentContent: function (stmts, attachmentMap) {
12391            var i,
12392                j;
12393
12394            for (i = 0; i < stmts.length; i += 1) {
12395                if (stmts[i].hasOwnProperty("attachments") && stmts[i].attachments !== null) {
12396                    for (j = 0; j < stmts[i].attachments.length; j += 1) {
12397                        if (attachmentMap.hasOwnProperty(stmts[i].attachments[j].sha2)) {
12398                            stmts[i].attachments[j].content = attachmentMap[stmts[i].attachments[j].sha2];
12399                        }
12400                    }
12401                }
12402            }
12403        },
12404
12405        /**
12406        Parses the different sections of a multipart/mixed response
12407
12408        @method _parseMultipart
12409        @private
12410        @param {String} [boundary] Boundary used to mark off the sections of the response
12411        @param {ArrayBuffer} [response] Body of the response
12412        @return {Array} Array of objects containing the parsed headers and body of each part
12413        */
12414        _parseMultipart: function (boundary, response) {
12415            /* global Uint8Array */
12416            var __boundary = "--" + boundary,
12417                byteArray,
12418                bodyEncodedInString,
12419                fullBodyEnd,
12420                sliceStart,
12421                sliceEnd,
12422                headerStart,
12423                headerEnd,
12424                bodyStart,
12425                bodyEnd,
12426                headers,
12427                body,
12428                parts = [],
12429                CRLF = 2;
12430
12431            //
12432            // treating the reponse as a stream of bytes and assuming that headers
12433            // and related mime boundaries are all US-ASCII (which is a safe assumption)
12434            // allows us to treat the whole response as a string when looking for offsets
12435            // but then slice on the raw array buffer
12436            //
12437            byteArray = new Uint8Array(response);
12438            bodyEncodedInString = this.__uint8ToString(byteArray);
12439
12440            fullBodyEnd = bodyEncodedInString.indexOf(__boundary + "--");
12441
12442            sliceStart = bodyEncodedInString.indexOf(__boundary);
12443            while (sliceStart !== -1) {
12444                sliceEnd = bodyEncodedInString.indexOf(__boundary, sliceStart + __boundary.length);
12445
12446                headerStart = sliceStart + __boundary.length + CRLF;
12447                headerEnd = bodyEncodedInString.indexOf("\r\n\r\n", sliceStart);
12448                bodyStart = headerEnd + CRLF + CRLF;
12449                bodyEnd = sliceEnd - 2;
12450
12451                headers = this._parseHeaders(
12452                    this.__uint8ToString(
12453                        new Uint8Array( response.slice(headerStart, headerEnd) )
12454                    )
12455                );
12456                body = response.slice(bodyStart, bodyEnd);
12457
12458                //
12459                // we know the first slice is the statement, and we know it is a string in UTF-8 (spec requirement)
12460                //
12461                if (parts.length === 0) {
12462                    body = TinCan.Utils.stringFromArrayBuffer(body);
12463                }
12464
12465                parts.push(
12466                    {
12467                        headers: headers,
12468                        body: body
12469                    }
12470                );
12471
12472                if (sliceEnd === fullBodyEnd) {
12473                    sliceStart = -1;
12474                }
12475                else {
12476                    sliceStart = sliceEnd;
12477                }
12478            }
12479
12480            return parts;
12481        },
12482
12483        //
12484        // implemented as a function to avoid 'RangeError: Maximum call stack size exceeded'
12485        // when calling .fromCharCode on the full byteArray which results in a too long
12486        // argument list for large arrays
12487        //
12488        __uint8ToString: function (byteArray) {
12489            var result = "",
12490                len = byteArray.byteLength,
12491                i;
12492
12493            for (i = 0; i < len; i += 1) {
12494                result += String.fromCharCode(byteArray[i]);
12495            }
12496            return result;
12497        },
12498
12499        /**
12500        Parses the headers of a multipart/mixed response section
12501
12502        @method _parseHeaders
12503        @private
12504        @param {String} [rawHeaders] String containing all the headers
12505        @return {Object} Map of the headers
12506        */
12507        _parseHeaders: function (rawHeaders) {
12508            var headers = {},
12509                headerList,
12510                key,
12511                h,
12512                i;
12513
12514            headerList = rawHeaders.split("\n");
12515            for (i = 0; i < headerList.length; i += 1) {
12516                h = headerList[i].split(":", 2);
12517
12518                if (h[1] !== null) {
12519                    headers[h[0]] = h[1].replace(/^\s+|\s+$/g, "");
12520
12521                    key = h[0];
12522                }
12523                else {
12524                    if (h[0].substring(0, 1) === "\t") {
12525                        headers[h[0]] = h[1].replace(/^\s+|\s+$/g, "");
12526                    }
12527                }
12528            }
12529
12530            return headers;
12531        },
12532
12533        /**
12534        Fetch more statements from a previous query, when used from a browser sends to the endpoint using the
12535        RESTful interface.  Use a callback to make the call asynchronous.
12536
12537        @method moreStatements
12538        @param {Object} [cfg] Configuration used to query
12539            @param {String} [cfg.url] More URL
12540            @param {Function} [cfg.callback] Callback to execute on completion
12541                @param {String|null} cfg.callback.err Error status or null if succcess
12542                @param {TinCan.StatementsResult|XHR} cfg.callback.response Receives a StatementsResult argument
12543        @return {Object} Request result
12544        */
12545        moreStatements: function (cfg) {
12546            this.log("moreStatements: " + cfg.url);
12547            var requestCfg,
12548                requestResult,
12549                callbackWrapper,
12550                parsedURL,
12551                serverRoot;
12552
12553            cfg = cfg || {};
12554
12555            // to support our interface (to support IE) we need to break apart
12556            // the more URL query params so that the request can be made properly later
12557            parsedURL = TinCan.Utils.parseURL(cfg.url, { allowRelative: true });
12558
12559            // Respect a more URL that is relative to either the server root
12560            // or endpoint (though only the former is allowed in the spec)
12561            serverRoot = TinCan.Utils.getServerRoot(this.endpoint);
12562            if (parsedURL.path.indexOf("/statements") === 0){
12563                parsedURL.path = this.endpoint.replace(serverRoot, "") + parsedURL.path;
12564                this.log("converting non-standard more URL to " + parsedURL.path);
12565            }
12566
12567            // The more relative URL might not start with a slash, add it if not
12568            if (parsedURL.path.indexOf("/") !== 0) {
12569                parsedURL.path = "/" + parsedURL.path;
12570            }
12571
12572            requestCfg = {
12573                method: "GET",
12574                // For arbitrary more URLs to work, we need to make the URL absolute here
12575                url: serverRoot + parsedURL.path,
12576                params: parsedURL.params
12577            };
12578            if (typeof cfg.callback !== "undefined") {
12579                callbackWrapper = function (err, xhr) {
12580                    var result = xhr;
12581
12582                    if (err === null) {
12583                        result = TinCan.StatementsResult.fromJSON(xhr.responseText);
12584                    }
12585
12586                    cfg.callback(err, result);
12587                };
12588                requestCfg.callback = callbackWrapper;
12589            }
12590
12591            requestResult = this.sendRequest(requestCfg);
12592            requestResult.config = requestCfg;
12593
12594            if (! callbackWrapper) {
12595                requestResult.statementsResult = null;
12596                if (requestResult.err === null) {
12597                    requestResult.statementsResult = TinCan.StatementsResult.fromJSON(requestResult.xhr.responseText);
12598                }
12599            }
12600
12601            return requestResult;
12602        },
12603
12604        /**
12605        Retrieve a state value, when used from a browser sends to the endpoint using the RESTful interface.
12606
12607        @method retrieveState
12608        @param {String} key Key of state to retrieve
12609        @param {Object} cfg Configuration options
12610            @param {TinCan.Activity} cfg.activity Activity in document identifier
12611            @param {TinCan.Agent} cfg.agent Agent in document identifier
12612          
12612  @param {String} [cfg.registration] Registration
12613            @param {Function} [cfg.callback] Callback to execute on completion
12614                @param {Object|Null} cfg.callback.error
12615                @param {TinCan.State|null} cfg.callback.result null if state is 404
12616            @param {Object} [cfg.requestHeaders] Object containing additional headers to add to request
12617        @return {TinCan.State|Object} TinCan.State retrieved when synchronous, or result from sendRequest
12618        */
12619        retrieveState: function (key, cfg) {
12620            this.log("retrieveState");
12621            var requestParams = {},
12622                requestCfg = {},
12623                requestResult,
12624                callbackWrapper,
12625                requestHeaders,
12626                self = this;
12627
12628            requestHeaders = cfg.requestHeaders || {};
12629
12630            requestParams = {
12631                stateId: key,
12632                activityId: cfg.activity.id
12633            };
12634            if (this.version === "0.9") {
12635                requestParams.actor = JSON.stringify(cfg.agent.asVersion(this.version));
12636            }
12637            else {
12638                requestParams.agent = JSON.stringify(cfg.agent.asVersion(this.version));
12639            }
12640            if ((typeof cfg.registration !== "undefined") && (cfg.registration !== null)) {
12641                if (this.version === "0.9") {
12642                    requestParams.registrationId = cfg.registration;
12643                }
12644                else {
12645                    requestParams.registration = cfg.registration;
12646                }
12647            }
12648
12649            requestCfg = {
12650                url: "activities/state",
12651                method: "GET",
12652                params: requestParams,
12653                ignore404: true,
12654                headers: requestHeaders
12655            };
12656
12657            if (typeof cfg.callback !== "undefined") {
12658                callbackWrapper = function (err, xhr) {
12659                    var result = xhr;
12660
12661                    if (err === null) {
12662                        if (xhr.status === 404) {
12663                            result = null;
12664                        }
12665                        else {
12666                            result = new TinCan.State(
12667                                {
12668                                    id: key,
12669                                    contents: xhr.responseText
12670                                }
12671                            );
12672                            if (typeof xhr.getResponseHeader !== "undefined" && xhr.getResponseHeader("ETag") !== null && xhr.getResponseHeader("ETag") !== "") {
12673                                result.etag = xhr.getResponseHeader("ETag");
12674                            }
12675                            else {
12676                                //
12677                                // either XHR didn't have getResponseHeader (probably cause it is an IE
12678                                // XDomainRequest object which doesn't) or not populated by LRS so create
12679                                // the hash ourselves
12680                                //
12681                                // the LRS is responsible for quoting the Etag value so we need to mimic
12682                                // that behavior here as well
12683                                //
12684                                result.etag = "\"" + TinCan.Utils.getSHA1String(xhr.responseText) + "\"";
12685                            }
12686
12687                            if (typeof xhr.contentType !== "undefined") {
12688                                // most likely an XDomainRequest which has .contentType,
12689                                // for the ones that it supports
12690                                result.contentType = xhr.contentType;
12691                            }
12692                            else if (typeof xhr.getResponseHeader !== "undefined" && xhr.getResponseHeader("Content-Type") !== null && xhr.getResponseHeader("Content-Type") !== "") {
12693                                result.contentType = xhr.getResponseHeader("Content-Type");
12694                            }
12695
12696                            if (TinCan.Utils.isApplicationJSON(result.contentType)) {
12697                                try {
12698                                    result.contents = JSON.parse(result.contents);
12699                                } catch (ex) {
12700                                    self.log("retrieveState - failed to deserialize JSON: " + ex);
12701                                }
12702                            }
12703                        }
12704                    }
12705
12706                    cfg.callback(err, result);
12707                };
12708                requestCfg.callback = callbackWrapper;
12709            }
12710
12711            requestResult = this.sendRequest(requestCfg);
12712            if (! callbackWrapper) {
12713                requestResult.state = null;
12714                if (requestResult.err === null && requestResult.xhr.status !== 404) {
12715                    requestResult.state = new TinCan.State(
12716                        {
12717                            id: key,
12718                            contents: requestResult.xhr.responseText
12719                        }
12720                    );
12721                    if (typeof requestResult.xhr.getResponseHeader !== "undefined" && requestResult.xhr.getResponseHeader("ETag") !== null && requestResult.xhr.getResponseHeader("ETag") !== "") {
12722                        requestResult.state.etag = requestResult.xhr.getResponseHeader("ETag");
12723                    }
12724                    else {
12725                        //
12726                        // either XHR didn't have getResponseHeader (probably cause it is an IE
12727                        // XDomainRequest object which doesn't) or not populated by LRS so create
12728                        // the hash ourselves
12729                        //
12730                        // the LRS is responsible for quoting the Etag value so we need to mimic
12731                        // that behavior here as well
12732                        //
12733                        requestResult.state.etag = "\"" + TinCan.Utils.getSHA1String(requestResult.xhr.responseText) + "\"";
12734                    }
12735                    if (typeof requestResult.xhr.contentType !== "undefined") {
12736                        // most likely an XDomainRequest which has .contentType
12737                        // for the ones that it supports
12738                        requestResult.state.contentType = requestResult.xhr.contentType;
12739                    }
12740                    else if (typeof requestResult.xhr.getResponseHeader !== "undefined" && requestResult.xhr.getResponseHeader("Content-Type") !== null && requestResult.xhr.getResponseHeader("Content-Type") !== "") {
12741                        requestResult.state.contentType = requestResult.xhr.getResponseHeader("Content-Type");
12742                    }
12743                    if (TinCan.Utils.isApplicationJSON(requestResult.state.contentType)) {
12744                        try {
12745                            requestResult.state.contents = JSON.parse(requestResult.state.contents);
12746                        } catch (ex) {
12747                            this.log("retrieveState - failed to deserialize JSON: " + ex);
12748                        }
12749                    }
12750                }
12751            }
12752
12753            return requestResult;
12754        },
12755
12756        /**
12757        Retrieve the list of IDs for a state, when used from a browser sends to the endpoint using the RESTful interface.
12758
12759        @method retrieveStateIds
12760        @param {Object} cfg Configuration options
12761            @param {TinCan.Activity} cfg.activity Activity in document identifier
12762            @param {TinCan.Agent} cfg.agent Agent in document identifier
12763            @param {String} [cfg.registration] Registration
12764            @param {Function} [cfg.callback] Callback to execute on completion
12765            @param {String} [cfg.since] Match activity profiles saved since given timestamp
12766            @param {Object}
12766 [cfg.requestHeaders] Optional object containing additional headers to add to request
12767        @return {Object} requestResult Request result
12768        */
12769        retrieveStateIds: function (cfg) {
12770            this.log("retrieveStateIds");
12771            var requestParams = {},
12772                requestCfg,
12773                requestHeaders,
12774                requestResult,
12775                callbackWrapper;
12776
12777            cfg = cfg || {};
12778            requestHeaders = cfg.requestHeaders || {};
12779
12780            requestParams.activityId = cfg.activity.id;
12781            if (this.version === "0.9") {
12782                requestParams.actor = JSON.stringify(cfg.agent.asVersion(this.version));
12783            }
12784            else {
12785                requestParams.agent = JSON.stringify(cfg.agent.asVersion(this.version));
12786            }
12787            if ((typeof cfg.registration !== "undefined") && (cfg.registration !== null)) {
12788                if (this.version === "0.9") {
12789                    requestParams.registrationId = cfg.registration;
12790                }
12791                else {
12792                    requestParams.registration = cfg.registration;
12793                }
12794            }
12795
12796            requestCfg = {
12797                url: "activities/state",
12798                method: "GET",
12799                params: requestParams,
12800                headers: requestHeaders,
12801                ignore404: true
12802            };
12803            if (typeof cfg.callback !== "undefined") {
12804                callbackWrapper = function (err, xhr) {
12805                    var result = xhr;
12806
12807                    if (err !== null) {
12808                        cfg.callback(err, result);
12809                        return;
12810                    }
12811
12812                    if (xhr.status === 404) {
12813                        result = [];
12814                    }
12815                    else {
12816                        try {
12817                            result = JSON.parse(xhr.responseText);
12818                        }
12819                        catch (ex) {
12820                            err = "Response JSON parse error: " + ex;
12821                        }
12822                    }
12823
12824                    cfg.callback(err, result);
12825                };
12826                requestCfg.callback = callbackWrapper;
12827            }
12828            if (typeof cfg.since !== "undefined") {
12829                requestCfg.params.since = cfg.since;
12830            }
12831
12832            requestResult = this.sendRequest(requestCfg);
12833            if (! callbackWrapper) {
12834                requestResult.profileIds = null;
12835                if (requestResult.err !== null) {
12836                    return requestResult;
12837                }
12838
12839                if (requestResult.xhr.status === 404) {
12840                    requestResult.profileIds = [];
12841                }
12842                else {
12843                    try {
12844                        requestResult.profileIds = JSON.parse(requestResult.xhr.responseText);
12845                    }
12846                    catch (ex) {
12847                        requestResult.err = "retrieveStateIds - JSON parse error: " + ex;
12848                    }
12849                }
12850            }
12851            return requestResult;
12852        },
12853
12854        /**
12855        Save a state value, when used from a browser sends to the endpoint using the RESTful interface.
12856
12857        @method saveState
12858        @param {String} key Key of state to save
12859        @param val Value to be stored
12860        @param {Object} cfg Configuration options
12861            @param {TinCan.Activity} cfg.activity Activity in document identifier
12862            @param {TinCan.Agent} cfg.agent Agent in document identifier
12863            @param {String} [cfg.registration] Registration
12864            @param {String} [cfg.lastSHA1] SHA1 of the previously seen existing state
12865            @param {String} [cfg.contentType] Content-Type to specify in headers (defaults to 'application/octet-stream')
12866            @param {String} [cfg.method] Method to use. Default: PUT
12867            @param {Function} [cfg.callback] Callback to execute on completion
12868            @param {Object} [cfg.requestHeaders] Optional object containing additional headers to add to request
12869        */
12870        saveState: function (key, val, cfg) {
12871            this.log("saveState");
12872            var requestParams,
12873                requestCfg,
12874                requestHeaders;
12875
12876            requestHeaders = cfg.requestHeaders || {};
12877
12878            if (typeof cfg.contentType === "undefined") {
12879                cfg.contentType = "application/octet-stream";
12880            }
12881            requestHeaders["Content-Type"] = cfg.contentType;
12882
12883            if (typeof val === "object" && TinCan.Utils.isApplicationJSON(cfg.contentType)) {
12884                val = JSON.stringify(val);
12885            }
12886
12887            if (typeof cfg.method === "undefined" || cfg.method !== "POST") {
12888                cfg.method = "PUT";
12889            }
12890
12891            requestParams = {
12892                stateId: key,
12893                activityId: cfg.activity.id
12894            };
12895            if (this.version === "0.9") {
12896                requestParams.actor = JSON.stringify(cfg.agent.asVersion(this.version));
12897            }
12898            else {
12899                requestParams.agent = JSON.stringify(cfg.agent.asVersion(this.version));
12900            }
12901            if ((typeof cfg.registration !== "undefined") && (cfg.registration !== null)) {
12902                if (this.version === "0.9") {
12903                    requestParams.registrationId = cfg.registration;
12904                }
12905                else {
12906                    requestParams.registration = cfg.registration;
12907                }
12908            }
12909
12910            requestCfg = {
12911                url: "activities/state",
12912                method: cfg.method,
12913                params: requestParams,
12914                data: val,
12915                headers: requestHeaders
12916            };
12917
12918            if (typeof cfg.callback !== "undefined") {
12919                requestCfg.callback = cfg.callback;
12920            }
12921            if (typeof cfg.lastSHA1 !== "undefined" && cfg.lastSHA1 !== null) {
12922                requestCfg.headers["If-Match"] = cfg.lastSHA1;
12923            }
12924
12925            return this.sendRequest(requestCfg);
12926        },
12927
12928        /**
12929        Drop a state value or all of the state, when used from a browser sends to the endpoint using the RESTful interface.
12930
12931        @method dropState
12932        @param {String|null} key Key of state to delete, or null for all
12933        @param {Object} cfg Configuration options
12934            @param {TinCan.Activity} cfg.activity Activity in document identifier
12935            @param {TinCan.Agent} cfg.agent Agent in document identifier
12936            @param {String} [cfg.registration] Registration
12937            @param {Function} [cfg.callback] Callback to execute on completion
12938            @param {Object} [cfg.requestHeaders] Optional object containing additional headers to add to request
12939        */
12940        dropState: function (key, cfg) {
12941            this.log("dropState");
12942            var requestParams,
12943                requestCfg,
12944                requestHeaders;
12945
12946            requestHeaders = cfg.requestHeaders || {};
12947
12948            requestParams = {
12949                activityId: cfg.activity.id
12950            };
12951            if (this.version === "0.9") {
12952                requestParams.actor = JSON.stringify(cfg.agent.asVersion(this.version));
12953            }
12954            else {
12955                requestParams.agent = JSON.stringify(cfg.agent.asVersion(this.version));
12956            }
12957            if (key !== null) {
12958                requestParams.stateId = key;
12959            }
12960            if ((typeof cfg.registration !== "undefined") && (cfg.registration !== null)) {
12961                if (this.version === "0.9") {
12962                    requestParams.registrationId = cfg.registration;
12963                }
12964                else {
12965                    requestParams.registration = cfg.registration;
12966                }
12967            }
12968
12969            requestCfg = {
12970                url: "activities/state",
12971                method: "DELETE",
12972                params: requestParams,
12973                headers: requestHeaders
12974            };
12975
12976            if (typeof cfg.callback !== "undefined") {
12977                requestCfg.callback = cfg.callback;
12978            }
12979
12980            return this.sendRequest(requestCfg);
12981        },
12982
12983        /**
12984        Retrieve an activity, when used from a browser sends to the endpoint using the RESTful interface.
12985
12986        @method retrieveActivity
12987        @param {String} activityId id of the Activity to retrieve
12988        @param {Object} cfg Configuration options
12989            @param {Function} [cfg.callback] Callback to execute on completion
12990            @param {Object}
12990 [cfg.requestHeaders] Optional object containing additional headers to add to request
12991        @return {Object} Value retrieved
12992        */
12993        retrieveActivity: function (activityId, cfg) {
12994            this.log("retrieveActivity");
12995            var requestCfg = {},
12996                requestResult,
12997                callbackWrapper,
12998                requestHeaders;
12999
13000            requestHeaders = cfg.requestHeaders || {};
13001
13002            requestCfg = {
13003                url: "activities",
13004                method: "GET",
13005                params: {
13006                    activityId: activityId
13007                },
13008                ignore404: true,
13009                headers: requestHeaders
13010            };
13011
13012            if (typeof cfg.callback !== "undefined") {
13013                callbackWrapper = function (err, xhr) {
13014                    var result = xhr;
13015
13016                    if (err === null) {
13017                        //
13018                        // a 404 really shouldn't happen because the LRS can dynamically
13019                        // build the response based on what has been passed to it, but
13020                        // don't have the client fail in the condition that it does, because
13021                        // we can do the same thing
13022                        //
13023                        if (xhr.status === 404) {
13024                            result = new TinCan.Activity(
13025                                {
13026                                    id: activityId
13027                                }
13028                            );
13029                        }
13030                        else {
13031                            result = TinCan.Activity.fromJSON(xhr.responseText);
13032                        }
13033                    }
13034
13035                    cfg.callback(err, result);
13036                };
13037                requestCfg.callback = callbackWrapper;
13038            }
13039
13040            requestResult = this.sendRequest(requestCfg);
13041            if (! callbackWrapper) {
13042                requestResult.activity = null;
13043                if (requestResult.err === null) {
13044                    if (requestResult.xhr.status === 404) {
13045                        requestResult.activity = new TinCan.Activity(
13046                            {
13047                                id: activityId
13048                            }
13049                        );
13050                    }
13051                    else {
13052                        requestResult.activity = TinCan.Activity.fromJSON(requestResult.xhr.responseText);
13053                    }
13054                }
13055            }
13056
13057            return requestResult;
13058        },
13059
13060        /**
13061        Retrieve an activity profile value, when used from a browser sends to the endpoint using the RESTful interface.
13062
13063        @method retrieveActivityProfile
13064        @param {String} key Key of activity profile to retrieve
13065        @param {Object} cfg Configuration options
13066            @param {TinCan.Activity} cfg.activity Activity in document identifier
13067            @param {Function} [cfg.callback] Callback to execute on completion
13068            @param {Object} [cfg.requestHeaders] Optional object containing additional headers to add to request
13069        @return {Object} Value retrieved
13070        */
13071        retrieveActivityProfile: function (key, cfg) {
13072            this.log("retrieveActivityProfile");
13073            var requestCfg = {},
13074                requestResult,
13075                callbackWrapper,
13076                requestHeaders,
13077                self = this;
13078
13079            requestHeaders = cfg.requestHeaders || {};
13080
13081            requestCfg = {
13082                url: "activities/profile",
13083                method: "GET",
13084                params: {
13085                    profileId: key,
13086                    activityId: cfg.activity.id
13087                },
13088                ignore404: true,
13089                headers: requestHeaders
13090            };
13091
13092            if (typeof cfg.callback !== "undefined") {
13093                callbackWrapper = function (err, xhr) {
13094                    var result = xhr;
13095
13096                    if (err === null) {
13097                        if (xhr.status === 404) {
13098                            result = null;
13099                        }
13100                        else {
13101                            result = new TinCan.ActivityProfile(
13102                                {
13103                                    id: key,
13104                                    activity: cfg.activity,
13105                                    contents: xhr.responseText
13106                                }
13107                            );
13108                            if (typeof xhr.getResponseHeader !== "undefined" && xhr.getResponseHeader("ETag") !== null && xhr.getResponseHeader("ETag") !== "") {
13109                                result.etag = xhr.getResponseHeader("ETag");
13110                            }
13111                            else {
13112                                //
13113                                // either XHR didn't have getResponseHeader (probably cause it is an IE
13114                                // XDomainRequest object which doesn't) or not populated by LRS so create
13115                                // the hash ourselves
13116                                //
13117                                // the LRS is responsible for quoting the Etag value so we need to mimic
13118                                // that behavior here as well
13119                                //
13120                                result.etag = "\"" + TinCan.Utils.getSHA1String(xhr.responseText) + "\"";
13121                            }
13122                            if (typeof xhr.contentType !== "undefined") {
13123                                // most likely an XDomainRequest which has .contentType
13124                                // for the ones that it supports
13125                                result.contentType = xhr.contentType;
13126                            }
13127                            else if (typeof xhr.getResponseHeader !== "undefined" && xhr.getResponseHeader("Content-Type") !== null && xhr.getResponseHeader("Content-Type") !== "") {
13128                                result.contentType = xhr.getResponseHeader("Content-Type");
13129                            }
13130                            if (TinCan.Utils.isApplicationJSON(result.contentType)) {
13131                                try {
13132                                    result.contents = JSON.parse(result.contents);
13133                                } catch (ex) {
13134                                    self.log("retrieveActivityProfile - failed to deserialize JSON: " + ex);
13135                                }
13136                            }
13137                        }
13138                    }
13139
13140                    cfg.callback(err, result);
13141                };
13142                requestCfg.callback = callbackWrapper;
13143            }
13144
13145            requestResult = this.sendRequest(requestCfg);
13146            if (! callbackWrapper) {
13147                requestResult.profile = null;
13148                if (requestResult.err === null && requestResult.xhr.status !== 404) {
13149                    requestResult.profile = new TinCan.ActivityProfile(
13150                        {
13151                            id: key,
13152                            activity: cfg.activity,
13153                            contents: requestResult.xhr.responseText
13154                        }
13155                    );
13156                    if (typeof requestResult.xhr.getResponseHeader !== "undefined" && requestResult.xhr.getResponseHeader("ETag") !== null && requestResult.xhr.getResponseHeader("ETag") !== "") {
13157                        requestResult.profile.etag = requestResult.xhr.getResponseHeader("ETag");
13158                    }
13159                    else {
13160                        //
13161                        // either XHR didn't have getResponseHeader (probably cause it is an IE
13162                        // XDomainRequest object which doesn't) or not populated by LRS so create
13163                        // the hash ourselves
13164                        //
13165                        // the LRS is responsible for quoting the Etag value so we need to mimic
13166                        // that behavior here as well
13167                        //
13168                        requestResult.profile.etag = "\"" + TinCan.Utils.getSHA1String(requestResult.xhr.responseText) + "\"";
13169                    }
13170                    if (typeof requestResult.xhr.contentType !== "undefined") {
13171                        // most likely an XDomainRequest which has .contentType
13172                        // for the ones that it supports
13173                        requestResult.profile.contentType = requestResult.xhr.contentType;
13174                    }
13175                    else if (typeof requestResult.xhr.getResponseHeader !== "undefined" && requestResult.xhr.getResponseHeader("Content-Type") !== null && requestResult.xhr.getResponseHeader("Content-Type") !== "") {
13176                        requestResult.profile.contentType = requestResult.xhr.getResponseHeader("Content-Type");
13177                    }
13178                    if (TinCan.Utils.isApplicationJSON(requestResult.profile.contentType)) {
13179                        try {
13180                            requestResult.profile.contents = JSON.parse(requestResult.profile.contents);
13181                        } catch (ex) {
13182                            this.log("retrieveActivityProfile - failed to deserialize JSON: " + ex);
13183                        }
13184                    }
13185                }
13186            }
13187
13188            return requestResult;
13189        },
13190
13191        /**
13192        Retrieve the list of IDs for an activity profile, when used from a browser sends to the endpoint using the RESTful interface.
13193
13194        @method retrieveActivityProfileIds
13195        @param {Object} cfg Configuration options
13196            @param {TinCan.Activity} cfg.activity Activity in document identifier
13197            @param {Function} [cfg.callback] Callback to execute on completion
13198            @param {String} [cfg.since] Match activity profiles saved since given timestamp
13199            @param {Object}
13199 [cfg.requestHeaders] Optional object containing additional headers to add to request
13200        @return {Array} List of ids for this Activity profile
13201        */
13202        retrieveActivityProfileIds: function (cfg) {
13203            this.log("retrieveActivityProfileIds");
13204            var requestCfg,
13205                requestHeaders,
13206                requestResult,
13207                callbackWrapper;
13208
13209            cfg = cfg || {};
13210            requestHeaders = cfg.requestHeaders || {};
13211
13212            requestCfg = {
13213                url: "activities/profile",
13214                method: "GET",
13215                params: {
13216                    activityId: cfg.activity.id
13217                },
13218                headers: requestHeaders,
13219                ignore404: true
13220            };
13221            if (typeof cfg.callback !== "undefined") {
13222                callbackWrapper = function (err, xhr) {
13223                    var result = xhr;
13224
13225                    if (err !== null) {
13226                        cfg.callback(err, result);
13227                        return;
13228                    }
13229
13230                    if (xhr.status === 404) {
13231                        result = [];
13232                    }
13233                    else {
13234                        try {
13235                            result = JSON.parse(xhr.responseText);
13236                        }
13237                        catch (ex) {
13238                            err = "Response JSON parse error: " + ex;
13239                        }
13240                    }
13241
13242                    cfg.callback(err, result);
13243                };
13244                requestCfg.callback = callbackWrapper;
13245            }
13246            if (typeof cfg.since !== "undefined") {
13247                requestCfg.params.since = cfg.since;
13248            }
13249
13250            requestResult = this.sendRequest(requestCfg);
13251            if (! callbackWrapper) {
13252                requestResult.profileIds = null;
13253                if (requestResult.err !== null) {
13254                    return requestResult;
13255                }
13256
13257                if (requestResult.xhr.status === 404) {
13258                    requestResult.profileIds = [];
13259                }
13260                else {
13261                    try {
13262                        requestResult.profileIds = JSON.parse(requestResult.xhr.responseText);
13263                    }
13264                    catch (ex) {
13265                        requestResult.err = "retrieveActivityProfileIds - JSON parse error: " + ex;
13266                    }
13267                }
13268            }
13269            return requestResult;
13270        },
13271
13272        /**
13273        Save an activity profile value, when used from a browser sends to the endpoint using the RESTful interface.
13274
13275        @method saveActivityProfile
13276        @param {String} key Key of activity profile to retrieve
13277        @param val Value to be stored
13278        @param {Object} cfg Configuration options
13279            @param {TinCan.Activity} cfg.activity Activity in document identifier
13280            @param {String} [cfg.lastSHA1] SHA1 of the previously seen existing profile
13281            @param {String} [cfg.contentType] Content-Type to specify in headers (defaults to 'application/octet-stream')
13282            @param {String} [cfg.method] Method to use. Default: PUT
13283            @param {Function} [cfg.callback] Callback to execute on completion
13284            @param {Object} [cfg.requestHeaders] Optional object containing additional headers to add to request
13285        */
13286        saveActivityProfile: function (key, val, cfg) {
13287            this.log("saveActivityProfile");
13288            var requestCfg,
13289                requestHeaders;
13290
13291            requestHeaders = cfg.requestHeaders || {};
13292
13293            if (typeof cfg.contentType === "undefined") {
13294                cfg.contentType = "application/octet-stream";
13295            }
13296            requestHeaders["Content-Type"] = cfg.contentType;
13297
13298            if (typeof cfg.method === "undefined" || cfg.method !== "POST") {
13299                cfg.method = "PUT";
13300            }
13301
13302            if (typeof val === "object" && TinCan.Utils.isApplicationJSON(cfg.contentType)) {
13303                val = JSON.stringify(val);
13304            }
13305
13306            requestCfg = {
13307                url: "activities/profile",
13308                method: cfg.method,
13309                params: {
13310                    profileId: key,
13311                    activityId: cfg.activity.id
13312                },
13313                data: val,
13314                headers: requestHeaders
13315            };
13316
13317            if (typeof cfg.callback !== "undefined") {
13318                requestCfg.callback = cfg.callback;
13319            }
13320            if (typeof cfg.lastSHA1 !== "undefined" && cfg.lastSHA1 !== null) {
13321                requestCfg.headers["If-Match"] = cfg.lastSHA1;
13322            }
13323            else {
13324                requestCfg.headers["If-None-Match"] = "*";
13325            }
13326
13327            return this.sendRequest(requestCfg);
13328        },
13329
13330        /**
13331        Drop an activity profile value, when used from a browser sends to the endpoint using the RESTful interface. Full activity profile
13332        delete is not supported by the spec.
13333
13334        @method dropActivityProfile
13335        @param {String|null} key Key of activity profile to delete
13336        @param {Object} cfg Configuration options
13337            @param {TinCan.Activity} cfg.activity Activity in document identifier
13338            @param {Function} [cfg.callback] Callback to execute on completion
13339            @param {Object} [cfg.requestHeaders] Optional object containing additional headers to add to request
13340        */
13341        dropActivityProfile: function (key, cfg) {
13342            this.log("dropActivityProfile");
13343            var requestParams,
13344                requestCfg,
13345                requestHeaders;
13346
13347            requestHeaders = cfg.requestHeaders || {};
13348
13349            requestParams = {
13350                profileId: key,
13351                activityId: cfg.activity.id
13352            };
13353
13354            requestCfg = {
13355                url: "activities/profile",
13356                method: "DELETE",
13357                params: requestParams,
13358                headers: requestHeaders
13359            };
13360
13361            if (typeof cfg.callback !== "undefined") {
13362                requestCfg.callback = cfg.callback;
13363            }
13364
13365            return this.sendRequest(requestCfg);
13366        },
13367
13368        /**
13369        Retrieve an agent profile value, when used from a browser sends to the endpoint using the RESTful interface.
13370
13371        @method retrieveAgentProfile
13372        @param {String} key Key of agent profile to retrieve
13373        @param {Object} cfg Configuration options
13374            @param {TinCan.Agent} cfg.agent Agent in document identifier
13375            @param {Function} [cfg.callback] Callback to execute on completion
13376            @param {Object} [cfg.requestHeaders] Optional object containing additional headers to add to request
13377        @return {Object} Value retrieved
13378        */
13379        retrieveAgentProfile: function (key, cfg) {
13380            this.log("retrieveAgentProfile");
13381            var requestCfg = {},
13382                requestResult,
13383                callbackWrapper,
13384                requestHeaders,
13385                self = this;
13386
13387            requestHeaders = cfg.requestHeaders || {};
13388
13389            requestCfg = {
13390                method: "GET",
13391                params: {
13392                    profileId: key
13393                },
13394                ignore404: true,
13395                headers: requestHeaders
13396            };
13397
13398            if (this.version === "0.9") {
13399                requestCfg.url = "actors/profile";
13400                requestCfg.params.actor = JSON.stringify(cfg.agent.asVersion(this.version));
13401            }
13402            else {
13403                requestCfg.url = "agents/profile";
13404                requestCfg.params.agent = JSON.stringify(cfg.agent.asVersion(this.version));
13405            }
13406            if (typeof cfg.callback !== "undefined") {
13407                callbackWrapper = function (err, xhr) {
13408                    var result = xhr;
13409
13410                    if (err === null) {
13411                        if (xhr.status === 404) {
13412                            result = null;
13413                        }
13414                        else {
13415                            result = new TinCan.AgentProfile(
13416                                {
13417                                    id: key,
13418                                    agent: cfg.agent,
13419                                    contents: xhr.responseText
13420                                }
13421                            );
13422                            if (typeof xhr.getResponseHeader !== "undefined" && xhr.getResponseHeader("ETag") !== null && xhr.getResponseHeader("ETag") !== "") {
13423                                result.etag = xhr.getResponseHeader("ETag");
13424                            }
13425                            else {
13426                                //
13427                                // either XHR didn't have getResponseHeader (probably cause it is an IE
13428                                // XDomainRequest object which doesn't) or not populated by LRS so create
13429                                // the hash ourselves
13430                                //
13431                                // the LRS is responsible for quoting the Etag value so we need to mimic
13432                                // that behavior here as well
13433                                //
13434                                result.etag = "\"" + TinCan.Utils.getSHA1String(xhr.responseText) + "\"";
13435                            }
13436                            if (typeof xhr.contentType !== "undefined") {
13437                                // most likely an XDomainRequest which has .contentType
13438                                // for the ones that it supports
13439                                result.contentType = xhr.contentType;
13440                            }
13441                            else if (typeof xhr.getResponseHeader !== "undefined" && xhr.getResponseHeader("Content-Type") !== null && xhr.getResponseHeader("Content-Type") !== "") {
13442                                result.contentType = xhr.getResponseHeader("Content-Type");
13443                            }
13444                            if (TinCan.Utils.isApplicationJSON(result.contentType)) {
13445                                try {
13446                                    result.contents = JSON.parse(result.contents);
13447                                } catch (ex) {
13448                                    self.log("retrieveAgentProfile - failed to deserialize JSON: " + ex);
13449                                }
13450                            }
13451                        }
13452                    }
13453
13454                    cfg.callback(err, result);
13455                };
13456                requestCfg.callback = callbackWrapper;
13457            }
13458
13459            requestResult = this.sendRequest(requestCfg);
13460            if (! callbackWrapper) {
13461                requestResult.profile = null;
13462                if (requestResult.err === null && requestResult.xhr.status !== 404) {
13463                    requestResult.profile = new TinCan.AgentProfile(
13464                        {
13465                            id: key,
13466                            agent: cfg.agent,
13467                            contents: requestResult.xhr.responseText
13468                        }
13469                    );
13470                    if (typeof requestResult.xhr.getResponseHeader !== "undefined" && requestResult.xhr.getResponseHeader("ETag") !== null && requestResult.xhr.getResponseHeader("ETag") !== "") {
13471                        requestResult.profile.etag = requestResult.xhr.getResponseHeader("ETag");
13472                    }
13473                    else {
13474                        //
13475                        // either XHR didn't have getResponseHeader (probably cause it is an IE
13476                        // XDomainRequest object which doesn't) or not populated by LRS so create
13477                        // the hash ourselves
13478                        //
13479                        // the LRS is responsible for quoting the Etag value so we need to mimic
13480                        // that behavior here as well
13481                        //
13482                        requestResult.profile.etag = "\"" + TinCan.Utils.getSHA1String(requestResult.xhr.responseText) + "\"";
13483                    }
13484                    if (typeof requestResult.xhr.contentType !== "undefined") {
13485                        // most likely an XDomainRequest which has .contentType
13486                        // for the ones that it supports
13487                        requestResult.profile.contentType = requestResult.xhr.contentType;
13488                    }
13489                    else if (typeof requestResult.xhr.getResponseHeader !== "undefined" && requestResult.xhr.getResponseHeader("Content-Type") !== null && requestResult.xhr.getResponseHeader("Content-Type") !== "") {
13490                        requestResult.profile.contentType = requestResult.xhr.getResponseHeader("Content-Type");
13491                    }
13492                    if (TinCan.Utils.isApplicationJSON(requestResult.profile.contentType)) {
13493                        try {
13494                            requestResult.profile.contents = JSON.parse(requestResult.profile.contents);
13495                        } catch (ex) {
13496                            this.log("retrieveAgentProfile - failed to deserialize JSON: " + ex);
13497                        }
13498                    }
13499                }
13500            }
13501
13502            return requestResult;
13503        },
13504
13505        /**
13506        Retrieve the list of profileIds for an agent profile, when used from a browser sends to the endpoint using the RESTful interface.
13507
13508        @method retrieveAgentProfileIds
13509        @param {Object} cfg Configuration options
13510            @param {TinCan.Agent} cfg.agent Agent in document identifier
13511            @param {Function} [cfg.callback] Callback to execute on completion
13512            @param {String} [cfg.since] Match activity profiles saved since given timestamp
13513            @param {Object}
13513 [cfg.requestHeaders] Optional object containing additional headers to add to request
13514        @return {Array} List of profileIds for this Agent
13515
13516        */
13517        retrieveAgentProfileIds: function (cfg) {
13518            this.log("retrieveAgentProfileIds");
13519            var requestParams = {},
13520                requestCfg,
13521                requestHeaders,
13522                requestResult,
13523                callbackWrapper;
13524
13525            cfg = cfg || {};
13526            requestHeaders = cfg.requestHeaders || {};
13527
13528            requestCfg = {
13529                method: "GET",
13530                params: requestParams,
13531                headers: requestHeaders,
13532                ignore404: true
13533            };
13534
13535            if (this.version === "0.9") {
13536                requestCfg.url = "actors/profile";
13537                requestParams.actor = JSON.stringify(cfg.agent.asVersion(this.version));
13538            }
13539            else {
13540                requestCfg.url = "agents/profile";
13541                requestParams.agent = JSON.stringify(cfg.agent.asVersion(this.version));
13542            }
13543            if (typeof cfg.callback !== "undefined") {
13544                callbackWrapper = function (err, xhr) {
13545                    var result = xhr;
13546
13547                    if (err !== null) {
13548                        cfg.callback(err, result);
13549                        return;
13550                    }
13551
13552                    if (xhr.status === 404) {
13553                        result = [];
13554                    }
13555                    else {
13556                        try {
13557                            result = JSON.parse(xhr.responseText);
13558                        }
13559                        catch (ex) {
13560                            err = "Response JSON parse error: " + ex;
13561                        }
13562                    }
13563
13564                    cfg.callback(err, result);
13565                };
13566                requestCfg.callback = callbackWrapper;
13567            }
13568            if (typeof cfg.since !== "undefined") {
13569                requestCfg.params.since = cfg.since;
13570            }
13571
13572            requestResult = this.sendRequest(requestCfg);
13573            if (! callbackWrapper) {
13574                requestResult.profileIds = null;
13575                if (requestResult.err !== null) {
13576                    return requestResult;
13577                }
13578
13579                if (requestResult.xhr.status === 404) {
13580                    requestResult.profileIds = [];
13581                }
13582                else {
13583                    try {
13584                        requestResult.profileIds = JSON.parse(requestResult.xhr.responseText);
13585                    }
13586                    catch (ex) {
13587                        requestResult.err = "retrieveAgentProfileIds - JSON parse error: " + ex;
13588                    }
13589                }
13590            }
13591            return requestResult;
13592        },
13593
13594        /**
13595        Save an agent profile value, when used from a browser sends to the endpoint using the RESTful interface.
13596
13597        @method saveAgentProfile
13598        @param {String} key Key of agent profile to retrieve
13599        @param val Value to be stored
13600        @param {Object} cfg Configuration options
13601            @param {TinCan.Agent} cfg.agent Agent in document identifier
13602            @param {String} [cfg.lastSHA1] SHA1 of the previously seen existing profile
13603            @param {String} [cfg.contentType] Content-Type to specify in headers (defaults to 'application/octet-stream')
13604            @param {String} [cfg.method] Method to use. Default: PUT
13605            @param {Function} [cfg.callback] Callback to execute on completion
13606            @param {Object} [cfg.requestHeaders] Optional object containing additional headers to add to request
13607        */
13608        saveAgentProfile: function (key, val, cfg) {
13609            this.log("saveAgentProfile");
13610            var requestCfg,
13611                requestHeaders;
13612
13613            requestHeaders = cfg.requestHeaders || {};
13614
13615            if (typeof cfg.contentType === "undefined") {
13616                cfg.contentType = "application/octet-stream";
13617            }
13618            requestHeaders["Content-Type"] = cfg.contentType;
13619
13620            if (typeof cfg.method === "undefined" || cfg.method !== "POST") {
13621                cfg.method = "PUT";
13622            }
13623
13624            if (typeof val === "object" && TinCan.Utils.isApplicationJSON(cfg.contentType)) {
13625                val = JSON.stringify(val);
13626            }
13627
13628            requestCfg = {
13629                method: cfg.method,
13630                params: {
13631                    profileId: key
13632                },
13633                data: val,
13634                headers: requestHeaders
13635            };
13636
13637            if (this.version === "0.9") {
13638                requestCfg.url = "actors/profile";
13639                requestCfg.params.actor = JSON.stringify(cfg.agent.asVersion(this.version));
13640            }
13641            else {
13642                requestCfg.url = "agents/profile";
13643                requestCfg.params.agent = JSON.stringify(cfg.agent.asVersion(this.version));
13644            }
13645            if (typeof cfg.callback !== "undefined") {
13646                requestCfg.callback = cfg.callback;
13647            }
13648            if (typeof cfg.lastSHA1 !== "undefined" && cfg.lastSHA1 !== null) {
13649                requestCfg.headers["If-Match"] = cfg.lastSHA1;
13650            }
13651            else {
13652                requestCfg.headers["If-None-Match"] = "*";
13653            }
13654
13655            return this.sendRequest(requestCfg);
13656        },
13657
13658        /**
13659        Drop an agent profile value, when used from a browser sends to the endpoint using the RESTful interface. Full agent profile
13660        delete is not supported by the spec.
13661
13662        @method dropAgentProfile
13663        @param {String|null} key Key of agent profile to delete
13664        @param {Object} cfg Configuration options
13665            @param {TinCan.Agent} cfg.agent Agent in document identifier
13666            @param {Function} [cfg.callback] Callback to execute on completion
13667            @param {Object} [cfg.requestHeaders] Optional object containing additional headers to add to request
13668        */
13669        dropAgentProfile: function (key, cfg) {
13670            this.log("dropAgentProfile");
13671            var requestParams,
13672                requestCfg,
13673                requestHeaders;
13674
13675            requestHeaders = cfg.requestHeaders || {};
13676
13677            requestParams = {
13678                profileId: key
13679            };
13680            requestCfg = {
13681                method: "DELETE",
13682                params: requestParams,
13683                headers: requestHeaders
13684            };
13685
13686            if (this.version === "0.9") {
13687                requestCfg.url = "actors/profile";
13688                requestParams.actor = JSON.stringify(cfg.agent.asVersion(this.version));
13689            }
13690            else {
13691                requestCfg.url = "agents/profile";
13692                requestParams.agent = JSON.stringify(cfg.agent.asVersion(this.version));
13693            }
13694            if (typeof cfg.callback !== "undefined") {
13695                requestCfg.callback = cfg.callback;
13696            }
13697
13698            return this.sendRequest(requestCfg);
13699        }
13700    };
13701
13702    /**
13703    Allows client code to determine whether their environment supports synchronous xhr handling
13704    @static this is a static property, set by the environment
13705    */
13706    LRS.syncEnabled = null;
13707}());
13708
13709/*
13710    Copyright 2012 Rustici Software
13711
13712    Licensed under the Apache License, Version 2.0 (the "License");
13713    you may not use this file except in compliance with the License.
13714    You may obtain a copy of the License at
13715
13716    http://www.apache.org/licenses/LICENSE-2.0
13717
13718    Unless required by applicable law or agreed to in writing, software
13719    distributed under the License is distributed on an "AS IS" BASIS,
13720    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13721    See the License for the specific language governing permissions and
13722    limitations under the License.
13723*/
13724
13725/**
13726TinCan client library
13727
13728@module TinCan
13729@submodule TinCan.AgentAccount
13730**/
13731(function () {
13732    "use strict";
13733
13734    /**
13735    @class TinCan.AgentAccount
13736    @constructor
13737    */
13738    var AgentAccount = TinCan.AgentAccount = function (cfg) {
13739        this.log("constructor");
13740
13741        /**
13742        @property homePage
13743        @type String
13744        */
13745        this.homePage = null;
13746
13747        /**
13748        @property name
13749        @type String
13750        */
13751        this.name = null;
13752
13753        this.init(cfg);
13754    };
13755    AgentAccount.prototype = {
13756        /**
13757        @property LOG_SRC
13758        */
13759        LOG_SRC: "AgentAccount",
13760
13761        /**
13762        @method log
13763        */
13764        log: TinCan.prototype.log,
13765
13766        /**
13767        @method init
13768        @param {Object} [options] Configuration used to initialize
13769        */
13770        init: function (cfg) {
13771            this.log("init");
13772            var i,
13773                directProps = [
13774                    "name",
13775                    "homePage"
13776                ];
13777
13778            cfg = cfg || {};
13779
13780            // handle .9 name changes
13781            if (typeof cfg.accountServiceHomePage !== "undefined") {
13782                cfg.homePage = cfg.accountServiceHomePage;
13783            }
13784            if (typeof cfg.accountName !== "undefined") {
13785                cfg.name = cfg.accountName;
13786            }
13787
13788            for (i = 0; i < directProps.length; i += 1) {
13789                if (cfg.hasOwnProperty(directProps[i]) && cfg[directProps[i]] !== null) {
13790                    this[directProps[i]] = cfg[directProps[i]];
13791                }
13792            }
13793        },
13794
13795        toString: function () {
13796            this.log("toString");
13797            var result = "";
13798
13799            if (this.name !== null || this.homePage !== null) {
13800                result += this.name !== null ? this.name : "-";
13801                result += ":";
13802                result += this.homePage !== null ? this.homePage : "-";
13803            }
13804            else {
13805                result = "AgentAccount: unidentified";
13806            }
13807
13808            return result;
13809        },
13810
13811        /**
13812        @method asVersion
13813        @param {String}
13813 [version] Version to return (defaults to newest supported)
13814        */
13815        asVersion: function (version) {
13816            this.log("asVersion: " + version);
13817            var result = {};
13818
13819            version = version || TinCan.versions()[0];
13820
13821            if (version === "0.9") {
13822                result.accountName = this.name;
13823                result.accountServiceHomePage = this.homePage;
13824            } else {
13825                result.name = this.name;
13826                result.homePage = this.homePage;
13827            }
13828
13829            return result;
13830        }
13831    };
13832
13833    /**
13834    @method fromJSON
13835    @return {Object} AgentAccount
13836    @static
13837    */
13838    AgentAccount.fromJSON = function (acctJSON) {
13839        AgentAccount.prototype.log("fromJSON");
13840        var _acct = JSON.parse(acctJSON);
13841
13842        return new AgentAccount(_acct);
13843    };
13844}());
13845
13846/*
13847    Copyright 2012 Rustici Software
13848
13849    Licensed under the Apache License, Version 2.0 (the "License");
13850    you may not use this file except in compliance with the License.
13851    You may obtain a copy of the License at
13852
13853    http://www.apache.org/licenses/LICENSE-2.0
13854
13855    Unless required by applicable law or agreed to in writing, software
13856    distributed under the License is distributed on an "AS IS" BASIS,
13857    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13858    See the License for the specific language governing permissions and
13859    limitations under the License.
13860*/
13861
13862/**
13863TinCan client library
13864
13865@module TinCan
13866@submodule TinCan.Agent
13867**/
13868(function () {
13869    "use strict";
13870
13871    /**
13872    @class TinCan.Agent
13873    @constructor
13874    */
13875    var Agent = TinCan.Agent = function (cfg) {
13876        this.log("constructor");
13877
13878        /**
13879        @property name
13880        @type String
13881        */
13882        this.name = null;
13883
13884        /**
13885        @property mbox
13886        @type String
13887        */
13888        this.mbox = null;
13889
13890        /**
13891        @property mbox_sha1sum
13892        @type String
13893        */
13894        this.mbox_sha1sum = null;
13895
13896        /**
13897        @property openid
13898        @type String
13899        */
13900        this.openid = null;
13901
13902        /**
13903        @property account
13904        @type TinCan.AgentAccount
13905        */
13906        this.account = null;
13907
13908        /**
13909        @property degraded
13910        @type Boolean
13911        @default false
13912        */
13913        this.degraded = false;
13914
13915        this.init(cfg);
13916    };
13917    Agent.prototype = {
13918        /**
13919        @property objectType
13920        @type String
13921        @default Agent
13922        */
13923        objectType: "Agent",
13924
13925        /**
13926        @property LOG_SRC
13927        */
13928        LOG_SRC: "Agent",
13929
13930        /**
13931        @method log
13932        */
13933        log: TinCan.prototype.log,
13934
13935        /**
13936        @method init
13937        @param {Object} [options] Configuration used to initialize
13938        */
13939        init: function (cfg) {
13940            this.log("init");
13941            var i,
13942                directProps = [
13943                    "name",
13944                    "mbox",
13945                    "mbox_sha1sum",
13946                    "openid"
13947                ],
13948                val
13949            ;
13950
13951            cfg = cfg || {};
13952
13953            // handle .9 split names and array properties into single interface
13954            if (typeof cfg.lastName !== "undefined" || typeof cfg.firstName !== "undefined") {
13955                cfg.name = "";
13956                if (typeof cfg.firstName !== "undefined" && cfg.firstName.length > 0) {
13957                    cfg.name = cfg.firstName[0];
13958                    if (cfg.firstName.length > 1) {
13959                        this.degraded = true;
13960                    }
13961                }
13962
13963                if (cfg.name !== "") {
13964                    cfg.name += " ";
13965                }
13966
13967                if (typeof cfg.lastName !== "undefined" && cfg.lastName.length > 0) {
13968                    cfg.name += cfg.lastName[0];
13969                    if (cfg.lastName.length > 1) {
13970                        this.degraded = true;
13971                    }
13972                }
13973            } else if (typeof cfg.familyName !== "undefined" || typeof cfg.givenName !== "undefined") {
13974                cfg.name = "";
13975                if (typeof cfg.givenName !== "undefined" && cfg.givenName.length > 0) {
13976                    cfg.name = cfg.givenName[0];
13977                    if (cfg.givenName.length > 1) {
13978                        this.degraded = true;
13979                    }
13980                }
13981
13982                if (cfg.name !== "") {
13983                    cfg.name += " ";
13984                }
13985
13986                if (typeof cfg.familyName !== "undefined" && cfg.familyName.length > 0) {
13987                    cfg.name += cfg.familyName[0];
13988                    if (cfg.familyName.length > 1) {
13989                        this.degraded = true;
13990                    }
13991                }
13992            }
13993
13994            if (typeof cfg.name === "object" && cfg.name !== null) {
13995                if (cfg.name.length > 1) {
13996                    this.degraded = true;
13997                }
13998                cfg.name = cfg.name[0];
13999            }
14000            if (typeof cfg.mbox === "object" && cfg.mbox !== null) {
14001                if (cfg.mbox.length > 1) {
14002                    this.degraded = true;
14003                }
14004                cfg.mbox = cfg.mbox[0];
14005            }
14006            if (typeof cfg.mbox_sha1sum === "object" && cfg.mbox_sha1sum !== null) {
14007                if (cfg.mbox_sha1sum.length > 1) {
14008                    this.degraded = true;
14009                }
14010                cfg.mbox_sha1sum = cfg.mbox_sha1sum[0];
14011            }
14012            if (typeof cfg.openid === "object" && cfg.openid !== null) {
14013                if (cfg.openid.length > 1) {
14014                    this.degraded = true;
14015                }
14016                cfg.openid = cfg.openid[0];
14017            }
14018            if (typeof cfg.account === "object" && cfg.account !== null && typeof cfg.account.homePage === "undefined" && typeof cfg.account.name === "undefined") {
14019                if (cfg.account.length === 0) {
14020                    delete cfg.account;
14021                }
14022                else {
14023                    if (cfg.account.length > 1) {
14024                        this.degraded = true;
14025                    }
14026                    cfg.account = cfg.account[0];
14027                }
14028            }
14029
14030            if (cfg.hasOwnProperty("account")) {
14031                if (cfg.account instanceof TinCan.AgentAccount) {
14032                    this.account = cfg.account;
14033                }
14034                else {
14035                    this.account = new TinCan.AgentAccount (cfg.account);
14036                }
14037            }
14038
14039            for (i = 0; i < directProps.length; i += 1) {
14040                if (cfg.hasOwnProperty(directProps[i]) && cfg[directProps[i]] !== null) {
14041                    val = cfg[directProps[i]];
14042                    if (directProps[i] === "mbox" && val.indexOf("mailto:") === -1) {
14043                        val = "mailto:" + val;
14044                    }
14045                    this[directProps[i]] = val;
14046                }
14047            }
14048        },
14049
14050        toString: function () {
14051            this.log("toString");
14052
14053            if (this.name !== null) {
14054                return this.name;
14055            }
14056            if (this.mbox !== null) {
14057                return this.mbox.replace("mailto:", "");
14058            }
14059            if (this.mbox_sha1sum !== null) {
14060                return this.mbox_sha1sum;
14061            }
14062            if (this.openid !== null) {
14063                return this.openid;
14064            }
14065            if (this.account !== null) {
14066                return this.account.toString();
14067            }
14068
14069            return this.objectType + ": unidentified";
14070        },
14071
14072        /**
14073        While a TinCan.Agent instance can store more than one inverse functional identifier
14074        this method will always only output one to be compliant with the statement sending
14075        specification. Order of preference is: mbox, mbox_sha1sum, openid, account
14076
14077        @method asVersion
14078        @param {String} [version] Version to return (defaults to newest supported)
14079        */
14080        asVersion: function (version) {
14081            this.log("asVersion: " + version);
14082            var result = {
14083                objectType: this.objectType
14084            };
14085
14086            version = version || TinCan.versions()[0];
14087
14088            if (version === "0.9") {
14089                if (this.mbox !== null) {
14090                    result.mbox = [ this.mbox ];
14091                }
14092                else if (this.mbox_sha1sum !== null) {
14093                    result.mbox_sha1sum = [ this.mbox_sha1sum ];
14094                }
14095                else if (this.openid !== null) {
14096                    result.openid = [ this.openid ];
14097                }
14098                else if (this.account !== null) {
14099                    result.account = [ this.account.asVersion(version) ];
14100                }
14101
14102                if (this.name !== null) {
14103                    result.name = [ this.name ];
14104                }
14105            } else {
14106                if (this.mbox !== null) {
14107                    result.mbox = this.mbox;
14108                }
14109                else if (this.mbox_sha1sum !== null) {
14110                    result.mbox_sha1sum = this.mbox_sha1sum;
14111                }
14112                else if (this.openid !== null) {
14113                    result.openid = this.openid;
14114                }
14115                else if (this.account !== null) {
14116                    result.account = this.account.asVersion(version);
14117                }
14118
14119                if (this.name !== null) {
14120                    result.name = this.name;
14121                }
14122            }
14123
14124            return result;
14125        }
14126    };
14127
14128    /**
14129    @method fromJSON
14130    @return {Object} Agent
14131    @static
14132    */
14133    Agent.fromJSON = function (agentJSON) {
14134        Agent.prototype.log("fromJSON");
14135        var _agent = JSON.parse(agentJSON);
14136
14137        return new Agent(_agent);
14138    };
14139}());
14140
14141/*
14142    Copyright 2012 Rustici Software
14143
14144    Licensed under the Apache License, Version 2.0 (the "License");
14145    you may not use this file except in compliance with the License.
14146    You may obtain a copy of the License at
14147
14148    http://www.apache.org/licenses/LICENSE-2.0
14149
14150    Unless required by applicable law or agreed to in writing, software
14151    distributed under the License is distributed on an "AS IS" BASIS,
14152    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14153    See the License for the specific language governing permissions and
14154    limitations under the License.
14155*/
14156
14157/**
14158TinCan client library
14159
14160@module TinCan
14161@submodule TinCan.Group
14162**/
14163(function () {
14164    "use strict";
14165
14166    /**
14167    @class TinCan.Group
14168    @constructor
14169    */
14170    var Group = TinCan.Group = function (cfg) {
14171        this.log("constructor");
14172
14173        /**
14174        @property name
14175        @type String
14176        */
14177        this.name = null;
14178
14179        /**
14180        @property mbox
14181        @type String
14182        */
14183        this.mbox = null;
14184
14185        /**
14186        @property mbox_sha1sum
14187        @type String
14188        */
14189        this.mbox_sha1sum = null;
14190
14191        /**
14192        @property openid
14193        @type String
14194        */
14195        this.openid = null;
14196
14197        /**
14198        @property account
14199        @type TinCan.AgentAccount
14200        */
14201        this.account = null;
14202
14203        /**
14204        @property member
14205        @type Array
14206        */
14207        this.member = [];
14208
14209        this.init(cfg);
14210    };
14211    Group.prototype = {
14212        /**
14213        @property objectType
14214        @type String
14215        @default "Group"
14216        @static
14217        */
14218        objectType: "Group",
14219
14220        /**
14221        @property LOG_SRC
14222        */
14223        LOG_SRC: "Group",
14224
14225        /**
14226        @method log
14227        */
14228        log: TinCan.prototype.log,
14229
14230        /**
14231        @method init
14232        @param {Object} [options] Configuration used to initialize
14233        */
14234        init: function (cfg) {
14235            this.log("init");
14236            var i;
14237
14238            cfg = cfg || {};
14239
14240            TinCan.Agent.prototype.init.call(this, cfg);
14241
14242            if (typeof cfg.member !== "undefined") {
14243                for (i = 0; i < cfg.member.length; i += 1) {
14244                    if (cfg.member[i] instanceof TinCan.Agent) {
14245                        this.member.push(cfg.member[i]);
14246                    }
14247                    else {
14248                        this.member.push(new TinCan.Agent (cfg.member[i]));
14249                    }
14250                }
14251            }
14252        },
14253
14254        toString: function (lang) {
14255            this.log("toString");
14256
14257            var result = TinCan.Agent.prototype.toString.call(this, lang);
14258            if (result !== this.objectType + ": unidentified") {
14259                result = this.objectType + ": " + result;
14260            }
14261
14262            return result;
14263        },
14264
14265        /**
14266        @method asVersion
14267        @param {String} [version] Version to return (defaults to newest supported)
14268        */
14269        asVersion: function (version) {
14270            this.log("asVersion: " + version);
14271            var result,
14272                i
14273            ;
14274
14275            version = version || TinCan.versions()[0];
14276
14277            result = TinCan.Agent.prototype.asVersion.call(this, version);
14278
14279            if (this.member.length > 0) {
14280                result.member = [];
14281                for (i = 0; i < this.member.length; i += 1) {
14282                    result.member.push(this.member[i].asVersion(version));
14283                }
14284            }
14285
14286            return result;
14287        }
14288    };
14289
14290    /**
14291    @method fromJSON
14292    @return {Object} Group
14293    @static
14294    */
14295    Group.fromJSON = function (groupJSON) {
14296        Group.prototype.log("fromJSON");
14297        var _group = JSON.parse(groupJSON);
14298
14299        return new Group(_group);
14300    };
14301}());
14302
14303/*
14304    Copyright 2012 Rustici Software
14305
14306    Licensed under the Apache License, Version 2.0 (the "License");
14307    you may not use this file except in compliance with the License.
14308    You may obtain a copy of the License at
14309
14310    http://www.apache.org/licenses/LICENSE-2.0
14311
14312    Unless required by applicable law or agreed to in writing, software
14313    distributed under the License is distributed on an "AS IS" BASIS,
14314    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14315    See the License for the specific language governing permissions and
14316    limitations under the License.
14317*/
14318
14319/**
14320TinCan client library
14321
14322@module TinCan
14323@submodule TinCan.Verb
14324*/
14325(function () {
14326    "use strict";
14327
14328    //
14329    // this represents the full set of verb values that were
14330    // allowed by the .9 spec version, if an object is created with one of
14331    // the short forms it will be upconverted to the matching long form,
14332    // for local storage and use and if an object is needed in .9 version
14333    // consequently down converted
14334    //
14335    // hopefully this list will never grow (or change) and only the exact
14336    // ADL compatible URLs should be matched
14337    //
14338    var _downConvertMap = {
14339        "http://adlnet.gov/expapi/verbs/experienced": "experienced",
14340        "http://adlnet.gov/expapi/verbs/attended":    "attended",
14341        "http://adlnet.gov/expapi/verbs/attempted":   "attempted",
14342        "http://adlnet.gov/expapi/verbs/completed":   "completed",
14343        "http://adlnet.gov/expapi/verbs/passed":      "passed",
14344        "http://adlnet.gov/expapi/verbs/failed":      "failed",
14345        "http://adlnet.gov/expapi/verbs/answered":    "answered",
14346        "http://adlnet.gov/expapi/verbs/interacted":  "interacted",
14347        "http://adlnet.gov/expapi/verbs/imported":    "imported",
14348        "http://adlnet.gov/expapi/verbs/created":     "created",
14349        "http://adlnet.gov/expapi/verbs/shared":      "shared",
14350        "http://adlnet.gov/expapi/verbs/voided":      "voided"
14351    },
14352
14353    /**
14354    @class TinCan.Verb
14355    @constructor
14356    */
14357    Verb = TinCan.Verb = function (cfg) {
14358        this.log("constructor");
14359
14360        /**
14361        @property id
14362        @type String
14363        */
14364        this.id = null;
14365
14366        /**
14367        @property display
14368        @type Object
14369        */
14370        this.display = null;
14371
14372        this.init(cfg);
14373    };
14374    Verb.prototype = {
14375        /**
14376        @property LOG_SRC
14377        */
14378        LOG_SRC: "Verb",
14379
14380        /**
14381        @method log
14382        */
14383        log: TinCan.prototype.log,
14384
14385        /**
14386        @method init
14387        @param {Object} [options] Configuration used to initialize
14388        */
14389        init: function (cfg) {
14390            this.log("init");
14391            var i,
14392                directProps = [
14393                    "id",
14394                    "display"
14395                ],
14396                prop
14397            ;
14398
14399            if (typeof cfg === "string") {
14400                this.id = cfg;
14401                this.display = {
14402                    und: this.id
14403                };
14404
14405                //If simple string like "attempted" was passed in (0.9 verbs),
14406                //upconvert the ID to the 0.95 ADL version
14407                for (prop in _downConvertMap) {
14408                    if (_downConvertMap.hasOwnProperty(prop) && _downConvertMap[prop] === cfg) {
14409                        this.id = prop;
14410                        break;
14411                    }
14412                }
14413            }
14414            else {
14415                cfg = cfg || {};
14416
14417                for (i = 0; i < directProps.length; i += 1) {
14418                    if (cfg.hasOwnProperty(directProps[i]) && cfg[directProps[i]] !== null) {
14419                        this[directProps[i]] = cfg[directProps[i]];
14420                    }
14421                }
14422
14423                if (this.display === null && typeof _downConvertMap[this.id] !== "undefined") {
14424                    this.display = {
14425                        "und": _downConvertMap[this.id]
14426                    };
14427                }
14428            }
14429        },
14430
14431        /**
14432        @method toString
14433        @return {String} String representation of the verb
14434        */
14435        toString: function (lang) {
14436            this.log("toString");
14437
14438            if (this.display !== null) {
14439                return this.getLangDictionaryValue("display", lang);
14440            }
14441
14442            return this.id;
14443        },
14444
14445        /**
14446        @method asVersion
14447        @param {String} [version] Version to return (defaults to newest supported)
14448        */
14449        asVersion: function (version) {
14450            this.log("asVersion");
14451            var result;
14452
14453            version = version || TinCan.versions()[0];
14454
14455            if (version === "0.9") {
14456                result = _downConvertMap[this.id];
14457            }
14458            else {
14459                result = {
14460                    id: this.id
14461                };
14462                if (this.display !== null) {
14463                    result.display = this.display;
14464                }
14465            }
14466
14467            return result;
14468        },
14469
14470        /**
14471        See {{#crossLink "TinCan.Utils/getLangDictionaryValue"}}{{/crossLink}}
14472
14473        @method getLangDictionaryValue
14474        */
14475        getLangDictionaryValue: TinCan.Utils.getLangDictionaryValue
14476    };
14477
14478    /**
14479    @method fromJSON
14480    @param {String} verbJSON String of JSON representing the verb
14481    @return {Object} Verb
14482    @static
14483    */
14484    Verb.fromJSON = function (verbJSON) {
14485        Verb.prototype.log("fromJSON");
14486        var _verb = JSON.parse(verbJSON);
14487
14488        return new Verb(_verb);
14489    };
14490}());
14491
14492/*
14493    Copyright 2012 Rustici Software
14494
14495    Licensed under the Apache License, Version 2.0 (the "License");
14496    you may not use this file except in compliance with the License.
14497    You may obtain a copy of the License at
14498
14499    http://www.apache.org/licenses/LICENSE-2.0
14500
14501    Unless required by applicable law or agreed to in writing, software
14502    distributed under the License is distributed on an "AS IS" BASIS,
14503    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14504    See the License for the specific language governing permissions and
14505    limitations under the License.
14506*/
14507
14508/**
14509TinCan client library
14510
14511@module TinCan
14512@submodule TinCan.Result
14513**/
14514(function () {
14515    "use strict";
14516
14517    /**
14518    @class TinCan.Result
14519    @constructor
14520    */
14521    var Result = TinCan.Result = function (cfg) {
14522        this.log("constructor");
14523
14524        /**
14525        @property score
14526        @type TinCan.Score|null
14527        */
14528        this.score = null;
14529
14530        /**
14531        @property success
14532        @type Boolean|null
14533        */
14534        this.success = null;
14535
14536        /**
14537        @property completion
14538        @type Boolean|null
14539        */
14540        this.completion = null;
14541
14542        /**
14543        @property duration
14544        @type String|null
14545        */
14546        this.duration = null;
14547
14548        /**
14549        @property response
14550        @type String|null
14551        */
14552        this.response = null;
14553
14554        /**
14555        @property extensions
14556        @type Object|null
14557        */
14558        this.extensions = null;
14559
14560        this.init(cfg);
14561    };
14562    Result.prototype = {
14563        /**
14564        @property LOG_SRC
14565        */
14566        LOG_SRC: "Result",
14567
14568        /**
14569        @method log
14570        */
14571        log: TinCan.prototype.log,
14572
14573        /**
14574        @method init
14575        @param {Object} [options] Configuration used to initialize
14576        */
14577        init: function (cfg) {
14578            this.log("init");
14579
14580            var i,
14581                directProps = [
14582                    "completion",
14583                    "duration",
14584                    "extensions",
14585                    "response",
14586                    "success"
14587                ]
14588            ;
14589
14590            cfg = cfg || {};
14591
14592            if (cfg.hasOwnProperty("score") && cfg.score !== null) {
14593                if (cfg.score instanceof TinCan.Score) {
14594                    this.score = cfg.score;
14595                }
14596                else {
14597                    this.score = new TinCan.Score (cfg.score);
14598                }
14599            }
14600
14601            for (i = 0; i < directProps.length; i += 1) {
14602                if (cfg.hasOwnProperty(directProps[i]) && cfg[directProps[i]] !== null) {
14603                    this[directProps[i]] = cfg[directProps[i]];
14604                }
14605            }
14606
14607            // 0.9 used a string, store it internally as a bool
14608            if (this.completion === "Completed") {
14609                this.completion = true;
14610            }
14611        },
14612
14613        /**
14614        @method asVersion
14615        @param {String} [version] Version to return (defaults to newest supported)
14616        */
14617        asVersion: function (version) {
14618            this.log("asVersion");
14619            var result = {},
14620                optionalDirectProps = [
14621                    "success",
14622                    "duration",
14623                    "response",
14624                    "extensions"
14625                ],
14626                optionalObjProps = [
14627                    "score"
14628                ],
14629                i;
14630
14631            version = version || TinCan.versions()[0];
14632
14633            for (i = 0; i < optionalDirectProps.length; i += 1) {
14634                if (this[optionalDirectProps[i]] !== null) {
14635                    result[optionalDirectProps[i]] = this[optionalDirectProps[i]];
14636                }
14637            }
14638            for (i = 0; i < optionalObjProps.length; i += 1) {
14639                if (this[optionalObjProps[i]] !== null) {
14640                    result[optionalObjProps[i]] = this[optionalObjProps[i]].asVersion(version);
14641                }
14642            }
14643            if (this.completion !== null) {
14644                if (version === "0.9") {
14645                    if (this.completion) {
14646                        result.completion = "Completed";
14647                    }
14648                }
14649                else {
14650                    result.completion = this.completion;
14651                }
14652            }
14653
14654            return result;
14655        }
14656    };
14657
14658    /**
14659    @method fromJSON
14660    @return {Object} Result
14661    @static
14662    */
14663    Result.fromJSON = function (resultJSON) {
14664        Result.prototype.log("fromJSON");
14665        var _result = JSON.parse(resultJSON);
14666
14667        return new Result(_result);
14668    };
14669}());
14670
14671/*
14672    Copyright 2012 Rustici Software
14673
14674    Licensed under the Apache License, Version 2.0 (the "License");
14675    you may not use this file except in compliance with the License.
14676    You may obtain a copy of the License at
14677
14678    http://www.apache.org/licenses/LICENSE-2.0
14679
14680    Unless required by applicable law or agreed to in writing, software
14681    distributed under the License is distributed on an "AS IS" BASIS,
14682    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14683    See the License for the specific language governing permissions and
14684    limitations under the License.
14685*/
14686
14687/**
14688TinCan client library
14689
14690@module TinCan
14691@submodule TinCan.Score
14692**/
14693(function () {
14694    "use strict";
14695
14696    /**
14697    @class TinCan.Score
14698    @constructor
14699    */
14700    var Score = TinCan.Score = function (cfg) {
14701        this.log("constructor");
14702
14703        /**
14704        @property scaled
14705        @type String
14706        */
14707        this.scaled = null;
14708
14709        /**
14710        @property raw
14711        @type String
14712        */
14713        this.raw = null;
14714
14715        /**
14716        @property min
14717        @type String
14718        */
14719        this.min = null;
14720
14721        /**
14722        @property max
14723        @type String
14724        */
14725        this.max = null;
14726
14727        this.init(cfg);
14728    };
14729    Score.prototype = {
14730        /**
14731        @property LOG_SRC
14732        */
14733        LOG_SRC: "Score",
14734
14735        /**
14736        @method log
14737        */
14738        log: TinCan.prototype.log,
14739
14740        /**
14741        @method init
14742        @param {Object} [options] Configuration used to initialize
14743        */
14744        init: function (cfg) {
14745            this.log("init");
14746
14747            var i,
14748                directProps = [
14749                    "scaled",
14750                    "raw",
14751                    "min",
14752                    "max"
14753                ]
14754            ;
14755
14756            cfg = cfg || {};
14757
14758            for (i = 0; i < directProps.length; i += 1) {
14759                if (cfg.hasOwnProperty(directProps[i]) && cfg[directProps[i]] !== null) {
14760                    this[directProps[i]] = cfg[directProps[i]];
14761                }
14762            }
14763        },
14764
14765        /**
14766        @method asVersion
14767        @param {String} [version] Version to return (defaults to newest supported)
14768        */
14769        asVersion: function (version) {
14770            this.log("asVersion");
14771            var result = {},
14772                optionalDirectProps = [
14773                    "scaled",
14774                    "raw",
14775                    "min",
14776                    "max"
14777                ],
14778                i;
14779
14780            version = version || TinCan.versions()[0];
14781
14782            for (i = 0; i < optionalDirectProps.length; i += 1) {
14783                if (this[optionalDirectProps[i]] !== null) {
14784                    result[optionalDirectProps[i]] = this[optionalDirectProps[i]];
14785                }
14786            }
14787
14788            return result;
14789        }
14790    };
14791
14792    /**
14793    @method fromJSON
14794    @return {Object} Score
14795    @static
14796    */
14797    Score.fromJSON = function (scoreJSON) {
14798        Score.prototype.log("fromJSON");
14799        var _score = JSON.parse(scoreJSON);
14800
14801        return new Score(_score);
14802    };
14803}());
14804
14805/*
14806    Copyright 2012 Rustici Software
14807
14808    Licensed under the Apache License, Version 2.0 (the "License");
14809    you may not use this file except in compliance with the License.
14810    You may obtain a copy of the License at
14811
14812    http://www.apache.org/licenses/LICENSE-2.0
14813
14814    Unless required by applicable law or agreed to in writing, software
14815    distributed under the License is distributed on an "AS IS" BASIS,
14816    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14817    See the License for the specific language governing permissions and
14818    limitations under the License.
14819*/
14820
14821/**
14822TinCan client library
14823
14824@module TinCan
14825@submodule TinCan.InteractionComponent
14826**/
14827(function () {
14828    "use strict";
14829
14830    /**
14831    @class TinCan.InteractionComponent
14832    @constructor
14833    */
14834    var InteractionComponent = TinCan.InteractionComponent = function (cfg) {
14835        this.log("constructor");
14836
14837        /**
14838        @property id
14839        @type String
14840        */
14841        this.id = null;
14842
14843        /**
14844        @property description
14845        @type Object
14846        */
14847        this.description = null;
14848
14849        this.init(cfg);
14850    };
14851    InteractionComponent.prototype = {
14852        /**
14853        @property LOG_SRC
14854        */
14855        LOG_SRC: "InteractionComponent",
14856
14857        /**
14858        @method log
14859        */
14860        log: TinCan.prototype.log,
14861
14862        /**
14863        @method init
14864        @param {Object} [options] Configuration used to initialize
14865        */
14866        init: function (cfg) {
14867            this.log("init");
14868            var i,
14869                directProps = [
14870                    "id",
14871                    "description"
14872                ]
14873            ;
14874
14875            cfg = cfg || {};
14876
14877            for (i = 0; i < directProps.length; i += 1) {
14878                if (cfg.hasOwnProperty(directProps[i]) && cfg[directProps[i]] !== null) {
14879                    this[directProps[i]] = cfg[directProps[i]];
14880                }
14881            }
14882        },
14883
14884        /**
14885        @method asVersion
14886        @param {String}
14886 [version] Version to return (defaults to newest supported)
14887        */
14888        asVersion: function (version) {
14889            this.log("asVersion");
14890            var result = {
14891                    id: this.id
14892                },
14893                optionalDirectProps = [
14894                    "description"
14895                ],
14896                i,
14897                prop;
14898
14899            version = version || TinCan.versions()[0];
14900
14901            for (i = 0; i < optionalDirectProps.length; i += 1) {
14902                prop = optionalDirectProps[i];
14903                if (this[prop] !== null) {
14904                    result[prop] = this[prop];
14905                }
14906            }
14907
14908            return result;
14909        },
14910
14911        /**
14912        See {{#crossLink "TinCan.Utils/getLangDictionaryValue"}}{{/crossLink}}
14913
14914        @method getLangDictionaryValue
14915        */
14916        getLangDictionaryValue: TinCan.Utils.getLangDictionaryValue
14917    };
14918
14919    /**
14920    @method fromJSON
14921    @return {Object} InteractionComponent
14922    @static
14923    */
14924    InteractionComponent.fromJSON = function (icJSON) {
14925        InteractionComponent.prototype.log("fromJSON");
14926        var _ic = JSON.parse(icJSON);
14927
14928        return new InteractionComponent(_ic);
14929    };
14930}());
14931
14932/*
14933    Copyright 2012 Rustici Software
14934
14935    Licensed under the Apache License, Version 2.0 (the "License");
14936    you may not use this file except in compliance with the License.
14937    You may obtain a copy of the License at
14938
14939    http://www.apache.org/licenses/LICENSE-2.0
14940
14941    Unless required by applicable law or agreed to in writing, software
14942    distributed under the License is distributed on an "AS IS" BASIS,
14943    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14944    See the License for the specific language governing permissions and
14945    limitations under the License.
14946*/
14947
14948/**
14949TinCan client library
14950
14951@module TinCan
14952@submodule TinCan.ActivityDefinition
14953**/
14954(function () {
14955    "use strict";
14956
14957    //
14958    // this represents the full set of activity definition types that were
14959    // allowed by the .9 spec version, if an object is created with one of
14960    // the short forms it will be upconverted to the matching long form,
14961    // for local storage and use and if an object is needed in .9 version
14962    // consequently down converted
14963    //
14964    // hopefully this list will never grow (or change) and only the exact
14965    // ADL compatible URLs should be matched
14966    //
14967    var _downConvertMap = {
14968        "http://adlnet.gov/expapi/activities/course": "course",
14969        "http://adlnet.gov/expapi/activities/module": "module",
14970        "http://adlnet.gov/expapi/activities/meeting": "meeting",
14971        "http://adlnet.gov/expapi/activities/media": "media",
14972        "http://adlnet.gov/expapi/activities/performance": "performance",
14973        "http://adlnet.gov/expapi/activities/simulation": "simulation",
14974        "http://adlnet.gov/expapi/activities/assessment": "assessment",
14975        "http://adlnet.gov/expapi/activities/interaction": "interaction",
14976        "http://adlnet.gov/expapi/activities/cmi.interaction": "cmi.interaction",
14977        "http://adlnet.gov/expapi/activities/question": "question",
14978        "http://adlnet.gov/expapi/activities/objective": "objective",
14979        "http://adlnet.gov/expapi/activities/link": "link"
14980    },
14981
14982    /**
14983    @class TinCan.ActivityDefinition
14984    @constructor
14985    */
14986    ActivityDefinition = TinCan.ActivityDefinition = function (cfg) {
14987        this.log("constructor");
14988
14989        /**
14990        @property name
14991        @type Object
14992        */
14993        this.name = null;
14994
14995        /**
14996        @property description
14997        @type Object
14998        */
14999        this.description = null;
15000
15001        /**
15002        @property type
15003        @type String
15004        */
15005        this.type = null;
15006
15007        /**
15008        @property moreInfo
15009        @type String
15010        */
15011        this.moreInfo = null;
15012
15013        /**
15014        @property extensions
15015        @type Object
15016        */
15017        this.extensions = null;
15018
15019        /**
15020        @property interactionType
15021        @type String
15022        */
15023        this.interactionType = null;
15024
15025        /**
15026        @property correctResponsesPattern
15027        @type Array
15028        */
15029        this.correctResponsesPattern = null;
15030
15031        /**
15032        @property choices
15033        @type Array
15034        */
15035        this.choices = null;
15036
15037        /**
15038        @property scale
15039        @type Array
15040        */
15041        this.scale = null;
15042
15043        /**
15044        @property source
15045        @type Array
15046        */
15047        this.source = null;
15048
15049        /**
15050        @property target
15051        @type Array
15052        */
15053        this.target = null;
15054
15055        /**
15056        @property steps
15057        @type Array
15058        */
15059        this.steps = null;
15060
15061        this.init(cfg);
15062    };
15063    ActivityDefinition.prototype = {
15064        /**
15065        @property LOG_SRC
15066        */
15067        LOG_SRC: "ActivityDefinition",
15068
15069        /**
15070        @method log
15071        */
15072        log: TinCan.prototype.log,
15073
15074        /**
15075        @method init
15076        @param {Object} [options] Configuration used to initialize
15077        */
15078        init: function (cfg) {
15079            this.log("init");
15080
15081            var i,
15082                j,
15083                prop,
15084                directProps = [
15085                    "name",
15086                    "description",
15087                    "moreInfo",
15088                    "extensions",
15089                    "correctResponsesPattern"
15090                ],
15091                interactionComponentProps = []
15092            ;
15093
15094            cfg = cfg || {};
15095
15096            if (cfg.hasOwnProperty("type") && cfg.type !== null) {
15097                // TODO: verify type is URI?
15098                for (prop in _downConvertMap) {
15099                    if (_downConvertMap.hasOwnProperty(prop) && _downConvertMap[prop] === cfg.type) {
15100                        cfg.type = _downConvertMap[prop];
15101                    }
15102                }
15103                this.type = cfg.type;
15104            }
15105
15106            if (cfg.hasOwnProperty("interactionType") && cfg.interactionType !== null) {
15107                // TODO: verify interaction type in acceptable set?
15108                this.interactionType = cfg.interactionType;
15109                if (cfg.interactionType === "choice" || cfg.interactionType === "sequencing") {
15110                    interactionComponentProps.push("choices");
15111                }
15112                else if (cfg.interactionType === "likert") {
15113                    interactionComponentProps.push("scale");
15114                }
15115                else if (cfg.interactionType === "matching") {
15116                    interactionComponentProps.push("source");
15117                    interactionComponentProps.push("target");
15118                }
15119                else if (cfg.interactionType === "performance") {
15120                    interactionComponentProps.push("steps");
15121                }
15122
15123                if (interactionComponentProps.length > 0) {
15124                    for (i = 0; i < interactionComponentProps.length; i += 1) {
15125                        prop = interactionComponentProps[i];
15126                        if (cfg.hasOwnProperty(prop) && cfg[prop] !== null) {
15127                            this[prop] = [];
15128                            for (j = 0; j < cfg[prop].length; j += 1) {
15129                                if (cfg[prop][j] instanceof TinCan.InteractionComponent) {
15130                                    this[prop].push(cfg[prop][j]);
15131                                } else {
15132                                    this[prop].push(
15133                                        new TinCan.InteractionComponent (
15134                                            cfg[prop][j]
15135                                        )
15136                                    );
15137                                }
15138                            }
15139                        }
15140                    }
15141                }
15142            }
15143
15144            for (i = 0; i < directProps.length; i += 1) {
15145                if (cfg.hasOwnProperty(directProps[i]) && cfg[directProps[i]] !== null) {
15146                    this[directProps[i]] = cfg[directProps[i]];
15147                }
15148            }
15149        },
15150
15151        /**
15152        @method toString
15153        @return {String} String representation of the definition
15154        */
15155        toString: function (lang) {
15156            this.log("toString");
15157
15158            if (this.name !== null) {
15159                return this.getLangDictionaryValue("name", lang);
15160            }
15161
15162            if (this.description !== null) {
15163                return this.getLangDictionaryValue("description", lang);
15164            }
15165
15166            return "";
15167        },
15168
15169        /**
15170        @method asVersion
15171        @param {String}
15171 [version] Version to return (defaults to newest supported)
15172        */
15173        asVersion: function (version) {
15174            this.log("asVersion");
15175            var result = {},
15176                directProps = [
15177                    "name",
15178                    "description",
15179                    "interactionType",
15180                    "correctResponsesPattern",
15181                    "extensions"
15182                ],
15183                interactionComponentProps = [
15184                    "choices",
15185                    "scale",
15186                    "source",
15187                    "target",
15188                    "steps"
15189                ],
15190                i,
15191                j,
15192                prop
15193            ;
15194
15195            version = version || TinCan.versions()[0];
15196
15197            if (this.type !== null) {
15198                if (version === "0.9") {
15199                    result.type = _downConvertMap[this.type];
15200                }
15201                else {
15202                    result.type = this.type;
15203                }
15204            }
15205
15206            for (i = 0; i < directProps.length; i += 1) {
15207                prop = directProps[i];
15208                if (this[prop] !== null) {
15209                    result[prop] = this[prop];
15210                }
15211            }
15212
15213            for (i = 0; i < interactionComponentProps.length; i += 1) {
15214                prop = interactionComponentProps[i];
15215                if (this[prop] !== null) {
15216                    result[prop] = [];
15217                    for (j = 0; j < this[prop].length; j += 1) {
15218                        result[prop].push(
15219                            this[prop][j].asVersion(version)
15220                        );
15221                    }
15222                }
15223            }
15224
15225            if (version.indexOf("0.9") !== 0) {
15226                if (this.moreInfo !== null) {
15227                    result.moreInfo = this.moreInfo;
15228                }
15229            }
15230
15231            return result;
15232        },
15233
15234        /**
15235        See {{#crossLink "TinCan.Utils/getLangDictionaryValue"}}{{/crossLink}}
15236
15237        @method getLangDictionaryValue
15238        */
15239        getLangDictionaryValue: TinCan.Utils.getLangDictionaryValue
15240    };
15241
15242    /**
15243    @method fromJSON
15244    @return {Object} ActivityDefinition
15245    @static
15246    */
15247    ActivityDefinition.fromJSON = function (definitionJSON) {
15248        ActivityDefinition.prototype.log("fromJSON");
15249        var _definition = JSON.parse(definitionJSON);
15250
15251        return new ActivityDefinition(_definition);
15252    };
15253}());
15254
15255/*
15256    Copyright 2012 Rustici Software
15257
15258    Licensed under the Apache License, Version 2.0 (the "License");
15259    you may not use this file except in compliance with the License.
15260    You may obtain a copy of the License at
15261
15262    http://www.apache.org/licenses/LICENSE-2.0
15263
15264    Unless required by applicable law or agreed to in writing, software
15265    distributed under the License is distributed on an "AS IS" BASIS,
15266    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15267    See the License for the specific language governing permissions and
15268    limitations under the License.
15269*/
15270
15271/**
15272TinCan client library
15273
15274@module TinCan
15275@submodule TinCan.Activity
15276**/
15277(function () {
15278    "use strict";
15279
15280    /**
15281    @class TinCan.Activity
15282    @constructor
15283    */
15284    var Activity = TinCan.Activity = function (cfg) {
15285        this.log("constructor");
15286
15287        /**
15288        @property objectType
15289        @type String
15290        @default Activity
15291        */
15292        this.objectType = "Activity";
15293
15294        /**
15295        @property id
15296        @type String
15297        */
15298        this.id = null;
15299
15300        /**
15301        @property definition
15302        @type TinCan.ActivityDefinition
15303        */
15304        this.definition = null;
15305
15306        this.init(cfg);
15307    };
15308    Activity.prototype = {
15309        /**
15310        @property LOG_SRC
15311        */
15312        LOG_SRC: "Activity",
15313
15314        /**
15315        @method log
15316        */
15317        log: TinCan.prototype.log,
15318
15319        /**
15320        @method init
15321        @param {Object} [options] Configuration used to initialize
15322        */
15323        init: function (cfg) {
15324            this.log("init");
15325
15326            var i,
15327                directProps = [
15328                    "id"
15329                ]
15330            ;
15331
15332            cfg = cfg || {};
15333
15334            if (cfg.hasOwnProperty("definition")) {
15335                if (cfg.definition instanceof TinCan.ActivityDefinition) {
15336                    this.definition = cfg.definition;
15337                } else {
15338                    this.definition = new TinCan.ActivityDefinition (cfg.definition);
15339                }
15340            }
15341
15342            for (i = 0; i < directProps.length; i += 1) {
15343                if (cfg.hasOwnProperty(directProps[i]) && cfg[directProps[i]] !== null) {
15344                    this[directProps[i]] = cfg[directProps[i]];
15345                }
15346            }
15347        },
15348
15349        /**
15350        @method toString
15351        @return {String} String representation of the activity
15352        */
15353        toString: function (lang) {
15354            this.log("toString");
15355            var defString = "";
15356
15357            if (this.definition !== null) {
15358                defString = this.definition.toString(lang);
15359                if (defString !== "") {
15360                    return defString;
15361                }
15362            }
15363
15364            if (this.id !== null) {
15365                return this.id;
15366            }
15367
15368            return "Activity: unidentified";
15369        },
15370
15371        /**
15372        @method asVersion
15373        @param {String}
15373 [version] Version to return (defaults to newest supported)
15374        */
15375        asVersion: function (version) {
15376            this.log("asVersion");
15377            var result = {
15378                id: this.id,
15379                objectType: this.objectType
15380            };
15381
15382            version = version || TinCan.versions()[0];
15383
15384            if (this.definition !== null) {
15385                result.definition = this.definition.asVersion(version);
15386            }
15387
15388            return result;
15389        }
15390    };
15391
15392    /**
15393    @method fromJSON
15394    @return {Object} Activity
15395    @static
15396    */
15397    Activity.fromJSON = function (activityJSON) {
15398        Activity.prototype.log("fromJSON");
15399        var _activity = JSON.parse(activityJSON);
15400
15401        return new Activity(_activity);
15402    };
15403}());
15404
15405/*
15406    Copyright 2013 Rustici Software
15407
15408    Licensed under the Apache License, Version 2.0 (the "License");
15409    you may not use this file except in compliance with the License.
15410    You may obtain a copy of the License at
15411
15412    http://www.apache.org/licenses/LICENSE-2.0
15413
15414    Unless required by applicable law or agreed to in writing, software
15415    distributed under the License is distributed on an "AS IS" BASIS,
15416    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15417    See the License for the specific language governing permissions and
15418    limitations under the License.
15419*/
15420
15421/**
15422TinCan client library
15423
15424@module TinCan
15425@submodule TinCan.ContextActivities
15426**/
15427(function () {
15428    "use strict";
15429
15430    /**
15431    @class TinCan.ContextActivities
15432    @constructor
15433    */
15434    var ContextActivities = TinCan.ContextActivities = function (cfg) {
15435        this.log("constructor");
15436
15437        /**
15438        @property category
15439        @type Array
15440        */
15441        this.category = null;
15442
15443        /**
15444        @property parent
15445        @type Array
15446        */
15447        this.parent = null;
15448
15449        /**
15450        @property grouping
15451        @type Array
15452        */
15453        this.grouping = null;
15454
15455        /**
15456        @property other
15457        @type Array
15458        */
15459        this.other = null;
15460
15461        this.init(cfg);
15462    };
15463    ContextActivities.prototype = {
15464        /**
15465        @property LOG_SRC
15466        */
15467        LOG_SRC: "ContextActivities",
15468
15469        /**
15470        @method log
15471        */
15472        log: TinCan.prototype.log,
15473
15474        /**
15475        @method init
15476        @param {Object} [options] Configuration used to initialize
15477        */
15478        init: function (cfg) {
15479            this.log("init");
15480
15481            var i,
15482                j,
15483                objProps = [
15484                    "category",
15485                    "parent",
15486                    "grouping",
15487                    "other"
15488                ],
15489                prop,
15490                val
15491            ;
15492
15493            cfg = cfg || {};
15494
15495            for (i = 0; i < objProps.length; i += 1) {
15496                prop = objProps[i];
15497                if (cfg.hasOwnProperty(prop) && cfg[prop] !== null) {
15498                    if (Object.prototype.toString.call(cfg[prop]) === "[object Array]") {
15499                        for (j = 0; j < cfg[prop].length; j += 1) {
15500                            this.add(prop, cfg[prop][j]);
15501                        }
15502                    }
15503                    else {
15504                        val = cfg[prop];
15505
15506                        this.add(prop, val);
15507                    }
15508                }
15509            }
15510        },
15511
15512        /**
15513        @method add
15514        @param String key Property to add value to one of "category", "parent", "grouping", "other"
15515        @return Number index where the value was added
15516        */
15517        add: function (key, val) {
15518            if (key !== "category" && key !== "parent" && key !== "grouping" && key !== "other") {
15519                return;
15520            }
15521
15522            if (this[key] === null) {
15523                this[key] = [];
15524            }
15525
15526            if (! (val instanceof TinCan.Activity)) {
15527                val = typeof val === "string" ? { id: val } : val;
15528                val = new TinCan.Activity (val);
15529            }
15530
15531            this[key].push(val);
15532
15533            return this[key].length - 1;
15534        },
15535
15536        /**
15537        @method asVersion
15538        @param {String} [version] Version to return (defaults to newest supported)
15539        */
15540        asVersion: function (version) {
15541            this.log("asVersion");
15542            var result = {},
15543                optionalObjProps = [
15544                    "parent",
15545                    "grouping",
15546                    "other"
15547                ],
15548                i,
15549                j;
15550
15551            version = version || TinCan.versions()[0];
15552
15553            for (i = 0; i < optionalObjProps.length; i += 1) {
15554                if (this[optionalObjProps[i]] !== null && this[optionalObjProps[i]].length > 0) {
15555                    if (version === "0.9" || version === "0.95") {
15556                        if (this[optionalObjProps[i]].length > 1) {
15557                            // TODO: exception?
15558                            this.log("[warning] version does not support multiple values in: " + optionalObjProps[i]);
15559                        }
15560
15561                        result[optionalObjProps[i]] = this[optionalObjProps[i]][0].asVersion(version);
15562                    }
15563                    else {
15564                        result[optionalObjProps[i]] = [];
15565                        for (j = 0; j < this[optionalObjProps[i]].length; j += 1) {
15566                            result[optionalObjProps[i]].push(
15567                                this[optionalObjProps[i]][j].asVersion(version)
15568                            );
15569                        }
15570                    }
15571                }
15572            }
15573            if (this.category !== null && this.category.length > 0) {
15574                if (version === "0.9" || version === "0.95") {
15575                    this.log("[error] version does not support the 'category' property: " + version);
15576                    throw new Error(version + " does not support the 'category' property");
15577                }
15578                else {
15579                    result.category = [];
15580                    for (i = 0; i < this.category.length; i += 1) {
15581                        result.category.push(this.category[i].asVersion(version));
15582                    }
15583                }
15584            }
15585
15586            return result;
15587        }
15588    };
15589
15590    /**
15591    @method fromJSON
15592    @return {Object} ContextActivities
15593    @static
15594    */
15595    ContextActivities.fromJSON = function (contextActivitiesJSON) {
15596        ContextActivities.prototype.log("fromJSON");
15597        var _contextActivities = JSON.parse(contextActivitiesJSON);
15598
15599        return new ContextActivities(_contextActivities);
15600    };
15601}());
15602
15603/*
15604    Copyright 2012 Rustici Software
15605
15606    Licensed under the Apache License, Version 2.0 (the "License");
15607    you may not use this file except in compliance with the License.
15608    You may obtain a copy of the License at
15609
15610    http://www.apache.org/licenses/LICENSE-2.0
15611
15612    Unless required by applicable law or agreed to in writing, software
15613    distributed under the License is distributed on an "AS IS" BASIS,
15614    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15615    See the License for the specific language governing permissions and
15616    limitations under the License.
15617*/
15618
15619/**
15620TinCan client library
15621
15622@module TinCan
15623@submodule TinCan.Context
15624**/
15625(function () {
15626    "use strict";
15627
15628    /**
15629    @class TinCan.Context
15630    @constructor
15631    */
15632    var Context = TinCan.Context = function (cfg) {
15633        this.log("constructor");
15634
15635        /**
15636        @property registration
15637        @type String|null
15638        */
15639        this.registration = null;
15640
15641        /**
15642        @property instructor
15643        @type TinCan.Agent|TinCan.Group|null
15644        */
15645        this.instructor = null;
15646
15647        /**
15648        @property team
15649        @type TinCan.Agent|TinCan.Group|null
15650        */
15651        this.team = null;
15652
15653        /**
15654        @property contextActivities
15655        @type ContextActivities|null
15656        */
15657        this.contextActivities = null;
15658
15659        /**
15660        @property revision
15661        @type String|null
15662        */
15663        this.revision = null;
15664
15665        /**
15666        @property platform
15667        @type Object|null
15668        */
15669        this.platform = null;
15670
15671        /**
15672        @property language
15673        @type String|null
15674        */
15675        this.language = null;
15676
15677        /**
15678        @property statement
15679        @type StatementRef|null
15680        */
15681        this.statement = null;
15682
15683        /**
15684        @property extensions
15685        @type String
15686        */
15687        this.extensions = null;
15688
15689        this.init(cfg);
15690    };
15691    Context.prototype = {
15692        /**
15693        @property LOG_SRC
15694        */
15695        LOG_SRC: "Context",
15696
15697        /**
15698        @method log
15699        */
15700        log: TinCan.prototype.log,
15701
15702        /**
15703        @method init
15704        @param {Object} [options] Configuration used to initialize
15705        */
15706        init: function (cfg) {
15707            this.log("init");
15708
15709            var i,
15710                directProps = [
15711                    "registration",
15712                    "revision",
15713                    "platform",
15714                    "language",
15715                    "extensions"
15716                ],
15717                agentGroupProps = [
15718                    "instructor",
15719                    "team"
15720                ],
15721                prop,
15722                val
15723            ;
15724
15725            cfg = cfg || {};
15726
15727            for (i = 0; i < directProps.length; i += 1) {
15728                prop = directProps[i];
15729                if (cfg.hasOwnProperty(prop) && cfg[prop] !== null) {
15730                    this[prop] = cfg[prop];
15731                }
15732            }
15733            for (i = 0; i < agentGroupProps.length; i += 1) {
15734                prop = agentGroupProps[i];
15735                if (cfg.hasOwnProperty(prop) && cfg[prop] !== null) {
15736                    val = cfg[prop];
15737
15738                    if (typeof val.objectType === "undefined" || val.objectType === "Person") {
15739                        val.objectType = "Agent";
15740                    }
15741
15742                    if (val.objectType === "Agent" && ! (val instanceof TinCan.Agent)) {
15743                        val = new TinCan.Agent (val);
15744                    } else if (val.objectType === "Group" && ! (val instanceof TinCan.Group)) {
15745                        val = new TinCan.Group (val);
15746                    }
15747
15748                    this[prop] = val;
15749                }
15750            }
15751
15752            if (cfg.hasOwnProperty("contextActivities") && cfg.contextActivities !== null) {
15753                if (cfg.contextActivities instanceof TinCan.ContextActivities) {
15754                    this.contextActivities = cfg.contextActivities;
15755                }
15756                else {
15757                    this.contextActivities = new TinCan.ContextActivities(cfg.contextActivities);
15758                }
15759            }
15760
15761            if (cfg.hasOwnProperty("statement") && cfg.statement !== null) {
15762                if (cfg.statement instanceof TinCan.StatementRef) {
15763                    this.statement = cfg.statement;
15764                }
15765                else if (cfg.statement instanceof TinCan.SubStatement) {
15766                    this.statement = cfg.statement;
15767                }
15768                else if (cfg.statement.objectType === "StatementRef") {
15769                    this.statement = new TinCan.StatementRef(cfg.statement);
15770                }
15771                else if (cfg.statement.objectType === "SubStatement") {
15772                    this.statement = new TinCan.SubStatement(cfg.statement);
15773                }
15774                else {
15775                    this.log("Unable to parse statement.context.statement property.");
15776                }
15777            }
15778        },
15779
15780        /**
15781        @method asVersion
15782        @param {String} [version] Version to return (defaults to newest supported)
15783        */
15784        asVersion: function (version) {
15785            this.log("asVersion");
15786            var result = {},
15787                optionalDirectProps = [
15788                    "registration",
15789                    "revision",
15790                    "platform",
15791                    "language",
15792                    "extensions"
15793                ],
15794                optionalObjProps = [
15795                    "instructor",
15796                    "team",
15797                    "contextActivities",
15798                    "statement"
15799                ],
15800                i;
15801
15802            version = version || TinCan.versions()[0];
15803
15804            if (this.statement instanceof TinCan.SubStatement && version !== "0.9" && version !== "0.95") {
15805                this.log("[error] version does not support SubStatements in the 'statement' property: " + version);
15806                throw new Error(version + " does not support SubStatements in the 'statement' property");
15807            }
15808
15809            for (i = 0; i < optionalDirectProps.length; i += 1) {
15810                if (this[optionalDirectProps[i]] !== null) {
15811                    result[optionalDirectProps[i]] = this[optionalDirectProps[i]];
15812                }
15813            }
15814            for (i = 0; i < optionalObjProps.length; i += 1) {
15815                if (this[optionalObjProps[i]] !== null) {
15816                    result[optionalObjProps[i]] = this[optionalObjProps[i]].asVersion(version);
15817                }
15818            }
15819
15820            return result;
15821        }
15822    };
15823
15824    /**
15825    @method fromJSON
15826    @return {Object} Context
15827    @static
15828    */
15829    Context.fromJSON = function (contextJSON) {
15830        Context.prototype.log("fromJSON");
15831        var _context = JSON.parse(contextJSON);
15832
15833        return new Context(_context);
15834    };
15835}());
15836
15837/*
15838    Copyright 2012 Rustici Software
15839
15840    Licensed under the Apache License, Version 2.0 (the "License");
15841    you may not use this file except in compliance with the License.
15842    You may obtain a copy of the License at
15843
15844    http://www.apache.org/licenses/LICENSE-2.0
15845
15846    Unless required by applicable law or agreed to in writing, software
15847    distributed under the License is distributed on an "AS IS" BASIS,
15848    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15849    See the License for the specific language governing permissions and
15850    limitations under the License.
15851*/
15852
15853/**
15854TinCan client library
15855
15856@module TinCan
15857@submodule TinCan.StatementRef
15858**/
15859(function () {
15860    "use strict";
15861
15862    /**
15863    @class TinCan.StatementRef
15864    @constructor
15865    @param {Object} [cfg] Configuration used to initialize.
15866        @param {Object} [cfg.id] ID of statement to reference
15867    **/
15868    var StatementRef = TinCan.StatementRef = function (cfg) {
15869        this.log("constructor");
15870
15871        /**
15872        @property id
15873        @type String
15874        */
15875        this.id = null;
15876
15877        this.init(cfg);
15878    };
15879
15880    StatementRef.prototype = {
15881        /**
15882        @property objectType
15883        @type String
15884        @default Agent
15885        */
15886        objectType: "StatementRef",
15887
15888        /**
15889        @property LOG_SRC
15890        */
15891        LOG_SRC: "StatementRef",
15892
15893        /**
15894        @method log
15895        */
15896        log: TinCan.prototype.log,
15897
15898        /**
15899        @method init
15900        @param {Object} [options] Configuration used to initialize (see constructor)
15901        */
15902        init: function (cfg) {
15903            this.log("init");
15904            var i,
15905                directProps = [
15906                    "id"
15907                ];
15908
15909            cfg = cfg || {};
15910
15911            for (i = 0; i < directProps.length; i += 1) {
15912                if (cfg.hasOwnProperty(directProps[i]) && cfg[directProps[i]] !== null) {
15913                    this[directProps[i]] = cfg[directProps[i]];
15914                }
15915            }
15916        },
15917
15918        /**
15919        @method toString
15920        @return {String} String representation of the statement
15921        */
15922        toString: function () {
15923            this.log("toString");
15924            return this.id;
15925        },
15926
15927        /**
15928        @method asVersion
15929        @param {String}
15929 [version] Version to return (defaults to newest supported)
15930        */
15931        asVersion: function (version) {
15932            this.log("asVersion");
15933            var result = {
15934                objectType: this.objectType,
15935                id: this.id
15936            };
15937
15938            if (version === "0.9") {
15939                result.objectType = "Statement";
15940            }
15941
15942            return result;
15943        }
15944    };
15945
15946    /**
15947    @method fromJSON
15948    @return {Object} StatementRef
15949    @static
15950    */
15951    StatementRef.fromJSON = function (stRefJSON) {
15952        StatementRef.prototype.log("fromJSON");
15953        var _stRef = JSON.parse(stRefJSON);
15954
15955        return new StatementRef(_stRef);
15956    };
15957}());
15958
15959/*
15960    Copyright 2012 Rustici Software
15961
15962    Licensed under the Apache License, Version 2.0 (the "License");
15963    you may not use this file except in compliance with the License.
15964    You may obtain a copy of the License at
15965
15966    http://www.apache.org/licenses/LICENSE-2.0
15967
15968    Unless required by applicable law or agreed to in writing, software
15969    distributed under the License is distributed on an "AS IS" BASIS,
15970    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15971    See the License for the specific language governing permissions and
15972    limitations under the License.
15973*/
15974
15975/**
15976TinCan client library
15977
15978@module TinCan
15979@submodule TinCan.SubStatement
15980**/
15981(function () {
15982    "use strict";
15983
15984    /**
15985    @class TinCan.SubStatement
15986    @constructor
15987    @param {Object} [cfg] Configuration used to initialize.
15988        @param {TinCan.Agent} [cfg.actor] Actor of statement
15989        @param {TinCan.Verb} [cfg.verb] Verb of statement
15990        @param {TinCan.Activity|TinCan.Agent} [cfg.object] Alias for 'target'
15991        @param {TinCan.Activity|TinCan.Agent} [cfg.target] Object of statement
15992        @param {TinCan.Result} [cfg.result] Statement Result
15993        @param {TinCan.Context} [cfg.context] Statement Context
15994    **/
15995    var SubStatement = TinCan.SubStatement = function (cfg) {
15996        this.log("constructor");
15997
15998        /**
15999        @property actor
16000        @type Object
16001        */
16002        this.actor = null;
16003
16004        /**
16005        @property verb
16006        @type Object
16007        */
16008        this.verb = null;
16009
16010        /**
16011        @property target
16012        @type Object
16013        */
16014        this.target = null;
16015
16016        /**
16017        @property result
16018        @type Object
16019        */
16020        this.result = null;
16021
16022        /**
16023        @property context
16024        @type Object
16025        */
16026        this.context = null;
16027
16028        /**
16029        @property timestamp
16030        @type Date
16031        */
16032        this.timestamp = null;
16033
16034        this.init(cfg);
16035    };
16036
16037    SubStatement.prototype = {
16038        /**
16039        @property objectType
16040        @type String
16041        @default Agent
16042        */
16043        objectType: "SubStatement",
16044
16045        /**
16046        @property LOG_SRC
16047        */
16048        LOG_SRC: "SubStatement",
16049
16050        /**
16051        @method log
16052        */
16053        log: TinCan.prototype.log,
16054
16055        /**
16056        @method init
16057        @param {Object} [options] Configuration used to initialize (see constructor)
16058        */
16059        init: function (cfg) {
16060            this.log("init");
16061            var i,
16062                directProps = [
16063                    "timestamp"
16064                ];
16065
16066            cfg = cfg || {};
16067
16068            if (cfg.hasOwnProperty("object")) {
16069                cfg.target = cfg.object;
16070            }
16071
16072            if (cfg.hasOwnProperty("actor")) {
16073                if (typeof cfg.actor.objectType === "undefined" || cfg.actor.objectType === "Person") {
16074                    cfg.actor.objectType = "Agent";
16075                }
16076
16077                if (cfg.actor.objectType === "Agent") {
16078                    if (cfg.actor instanceof TinCan.Agent) {
16079                        this.actor = cfg.actor;
16080                    } else {
16081                        this.actor = new TinCan.Agent (cfg.actor);
16082                    }
16083                } else if (cfg.actor.objectType === "Group") {
16084                    if (cfg.actor instanceof TinCan.Group) {
16085                        this.actor = cfg.actor;
16086                    } else {
16087                        this.actor = new TinCan.Group (cfg.actor);
16088                    }
16089                }
16090            }
16091            if (cfg.hasOwnProperty("verb")) {
16092                if (cfg.verb instanceof TinCan.Verb) {
16093                    this.verb = cfg.verb;
16094                } else {
16095                    this.verb = new TinCan.Verb (cfg.verb);
16096                }
16097            }
16098            if (cfg.hasOwnProperty("target")) {
16099                if (cfg.target instanceof TinCan.Activity ||
16100                    cfg.target instanceof TinCan.Agent ||
16101                    cfg.target instanceof TinCan.Group ||
16102                    cfg.target instanceof TinCan.SubStatement ||
16103                    cfg.target instanceof TinCan.StatementRef
16104                ) {
16105                    this.target = cfg.target;
16106                } else {
16107                    if (typeof cfg.target.objectType === "undefined") {
16108                        cfg.target.objectType = "Activity";
16109                    }
16110
16111                    if (cfg.target.objectType === "Activity") {
16112                        this.target = new TinCan.Activity (cfg.target);
16113                    } else if (cfg.target.objectType === "Agent") {
16114                        this.target = new TinCan.Agent (cfg.target);
16115                    } else if (cfg.target.objectType === "Group") {
16116                        this.target = new TinCan.Group (cfg.target);
16117                    } else if (cfg.target.objectType === "SubStatement") {
16118                        this.target = new TinCan.SubStatement (cfg.target);
16119                    } else if (cfg.target.objectType === "StatementRef") {
16120                        this.target = new TinCan.StatementRef (cfg.target);
16121                    } else {
16122                        this.log("Unrecognized target type: " + cfg.target.objectType);
16123                    }
16124                }
16125            }
16126            if (cfg.hasOwnProperty("result")) {
16127                if (cfg.result instanceof TinCan.Result) {
16128                    this.result = cfg.result;
16129                } else {
16130                    this.result = new TinCan.Result (cfg.result);
16131                }
16132            }
16133            if (cfg.hasOwnProperty("context")) {
16134                if (cfg.context instanceof TinCan.Context) {
16135                    this.context = cfg.context;
16136                } else {
16137                    this.context = new TinCan.Context (cfg.context);
16138                }
16139            }
16140
16141            for (i = 0; i < directProps.length; i += 1) {
16142                if (cfg.hasOwnProperty(directProps[i]) && cfg[directProps[i]] !== null) {
16143                    this[directProps[i]] = cfg[directProps[i]];
16144                }
16145            }
16146        },
16147
16148        /**
16149        @method toString
16150        @return {String} String representation of the statement
16151        */
16152        toString: function (lang) {
16153            this.log("toString");
16154            return (this.actor !== null ? this.actor.toString(lang) : "") +
16155                    " " +
16156                    (this.verb !== null ? this.verb.toString(lang) : "") +
16157                    " " +
16158                    (this.target !== null ? this.target.toString(lang) : "");
16159        },
16160
16161        /**
16162        @method asVersion
16163        @param {String} [version] Version to return (defaults to newest supported)
16164        */
16165        asVersion: function (version) {
16166            this.log("asVersion");
16167            var result,
16168                optionalDirectProps = [
16169                    "timestamp"
16170                ],
16171                optionalObjProps = [
16172                    "actor",
16173                    "verb",
16174                    "result",
16175                    "context"
16176                ],
16177                i;
16178
16179            result = {
16180                objectType: this.objectType
16181            };
16182            version = version || TinCan.versions()[0];
16183
16184            for (i = 0; i < optionalDirectProps.length; i += 1) {
16185                if (this[optionalDirectProps[i]] !== null) {
16186                    result[optionalDirectProps[i]] = this[optionalDirectProps[i]];
16187                }
16188            }
16189            for (i = 0; i < optionalObjProps.length; i += 1) {
16190                if (this[optionalObjProps[i]] !== null) {
16191                    result[optionalObjProps[i]] = this[optionalObjProps[i]].asVersion(version);
16192                }
16193            }
16194            if (this.target !== null) {
16195                result.object = this.target.asVersion(version);
16196            }
16197
16198            if (version === "0.9") {
16199                result.objectType = "Statement";
16200            }
16201
16202            return result;
16203        }
16204    };
16205
16206    /**
16207    @method fromJSON
16208    @return {Object} SubStatement
16209    @static
16210    */
16211    SubStatement.fromJSON = function (subStJSON) {
16212        SubStatement.prototype.log("fromJSON");
16213        var _subSt = JSON.parse(subStJSON);
16214
16215        return new SubStatement(_subSt);
16216    };
16217}());
16218
16219/*
16220    Copyright 2012-3 Rustici Software
16221
16222    Licensed under the Apache License, Version 2.0 (the "License");
16223    you may not use this file except in compliance with the License.
16224    You may obtain a copy of the License at
16225
16226    http://www.apache.org/licenses/LICENSE-2.0
16227
16228    Unless required by applicable law or agreed to in writing, software
16229    distributed under the License is distributed on an "AS IS" BASIS,
16230    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16231    See the License for the specific language governing permissions and
16232    limitations under the License.
16233*/
16234
16235/**
16236TinCan client library
16237
16238@module TinCan
16239@submodule TinCan.Statement
16240**/
16241(function () {
16242    "use strict";
16243
16244    /**
16245    @class TinCan.Statement
16246    @constructor
16247    @param {Object} [cfg] Values to set in properties
16248        @param {String} [cfg.id] Statement ID (UUID)
16249        @param {TinCan.Agent} [cfg.actor] Actor of statement
16250        @param {TinCan.Verb} [cfg.verb] Verb of statement
16251        @param {TinCan.Activity|TinCan.Agent|TinCan.Group|TinCan.StatementRef|TinCan.SubStatement} [cfg.object] Alias for 'target'
16252        @param {TinCan.Activity|TinCan.Agent|TinCan.Group|TinCan.StatementRef|TinCan.SubStatement} [cfg.target] Object of statement
16253        @param {TinCan.Result} [cfg.result] Statement Result
16254        @param {TinCan.Context} [cfg.context] Statement Context
16255        @param {TinCan.Agent} [cfg.authority] Statement Authority
16256        @param {TinCan.Attachment} [cfg.attachments] Statement Attachments
16257        @param {String} [cfg.timestamp] ISO8601 Date/time value
16258        @param {String} [cfg.stored] ISO8601 Date/time value
16259        @param {String} [cfg.version] Version of the statement (post 0.95)
16260    @param {Object} [initCfg] Configuration of initialization process
16261        @param {Integer} [initCfg.storeOriginal] Whether to store a JSON stringified version
16262            of the original options object, pass number of space
16262s used for indent
16263        @param {Boolean} [initCfg.doStamp] Whether to automatically set the 'id' and 'timestamp'
16264            properties (default: true)
16265    **/
16266    var Statement = TinCan.Statement = function (cfg, initCfg) {
16267        this.log("constructor");
16268
16269        // check for true value for API backwards compat
16270        if (typeof initCfg === "number") {
16271            initCfg = {
16272                storeOriginal: initCfg
16273            };
16274        } else {
16275            initCfg = initCfg || {};
16276        }
16277        if (typeof initCfg.storeOriginal === "undefined") {
16278            initCfg.storeOriginal = null;
16279        }
16280        if (typeof initCfg.doStamp === "undefined") {
16281            initCfg.doStamp = true;
16282        }
16283
16284        /**
16285        @property id
16286        @type String
16287        */
16288        this.id = null;
16289
16290        /**
16291        @property actor
16292        @type TinCan.Agent|TinCan.Group|null
16293        */
16294        this.actor = null;
16295
16296        /**
16297        @property verb
16298        @type TinCan.Verb|null
16299        */
16300        this.verb = null;
16301
16302        /**
16303        @property target
16304        @type TinCan.Activity|TinCan.Agent|TinCan.Group|TinCan.StatementRef|TinCan.SubStatement|null
16305        */
16306        this.target = null;
16307
16308        /**
16309        @property result
16310        @type Object
16311        */
16312        this.result = null;
16313
16314        /**
16315        @property context
16316        @type Object
16317        */
16318        this.context = null;
16319
16320        /**
16321        @property timestamp
16322        @type String
16323        */
16324        this.timestamp = null;
16325
16326        /**
16327        @property stored
16328        @type String
16329        */
16330        this.stored = null;
16331
16332        /**
16333        @property authority
16334        @type TinCan.Agent|null
16335        */
16336        this.authority = null;
16337
16338        /**
16339        @property attachments
16340        @type Array of TinCan.Attachment
16341        */
16342        this.attachments = null;
16343
16344        /**
16345        @property version
16346        @type String
16347        */
16348        this.version = null;
16349
16350        /**
16351        @property degraded
16352        @type Boolean
16353        @default false
16354        */
16355        this.degraded = false;
16356
16357        /**
16358        @property voided
16359        @type Boolean
16360        @default null
16361        @deprecated
16362        */
16363        this.voided = null;
16364
16365        /**
16366        @property inProgress
16367        @type Boolean
16368        @deprecated
16369        */
16370        this.inProgress = null;
16371
16372        /**
16373        @property originalJSON
16374        @type String
16375        */
16376        this.originalJSON = null;
16377
16378        this.init(cfg, initCfg);
16379    };
16380
16381    Statement.prototype = {
16382        /**
16383        @property LOG_SRC
16384        */
16385        LOG_SRC: "Statement",
16386
16387        /**
16388        @method log
16389        */
16390        log: TinCan.prototype.log,
16391
16392        /**
16393        @method init
16394        @param {Object} [properties] Configuration used to set properties (see constructor)
16395        @param {Object} [cfg] Configuration used to initialize (see constructor)
16396        */
16397        init: function (cfg, initCfg) {
16398            this.log("init");
16399            var i,
16400                directProps = [
16401                    "id",
16402                    "stored",
16403                    "timestamp",
16404                    "version",
16405                    "inProgress",
16406                    "voided"
16407                ];
16408
16409            cfg = cfg || {};
16410
16411            if (initCfg.storeOriginal) {
16412                this.originalJSON = JSON.stringify(cfg, null, initCfg.storeOriginal);
16413            }
16414
16415            if (cfg.hasOwnProperty("object")) {
16416                cfg.target = cfg.object;
16417            }
16418
16419            if (cfg.hasOwnProperty("actor")) {
16420                if (typeof cfg.actor.objectType === "undefined" || cfg.actor.objectType === "Person") {
16421                    cfg.actor.objectType = "Agent";
16422                }
16423
16424                if (cfg.actor.objectType === "Agent") {
16425                    if (cfg.actor instanceof TinCan.Agent) {
16426                        this.actor = cfg.actor;
16427                    } else {
16428                        this.actor = new TinCan.Agent (cfg.actor);
16429                    }
16430                } else if (cfg.actor.objectType === "Group") {
16431                    if (cfg.actor instanceof TinCan.Group) {
16432                        this.actor = cfg.actor;
16433                    } else {
16434                        this.actor = new TinCan.Group (cfg.actor);
16435                    }
16436                }
16437            }
16438            if (cfg.hasOwnProperty("authority")) {
16439                if (typeof cfg.authority.objectType === "undefined" || cfg.authority.objectType === "Person") {
16440                    cfg.authority.objectType = "Agent";
16441                }
16442
16443                if (cfg.authority.objectType === "Agent") {
16444                    if (cfg.authority instanceof TinCan.Agent) {
16445                        this.authority = cfg.authority;
16446                    } else {
16447                        this.authority = new TinCan.Agent (cfg.authority);
16448                    }
16449                } else if (cfg.authority.objectType === "Group") {
16450                    if (cfg.actor instanceof TinCan.Group) {
16451                        this.authority = cfg.authority;
16452                    } else {
16453                        this.authority = new TinCan.Group (cfg.authority);
16454                    }
16455                }
16456            }
16457            if (cfg.hasOwnProperty("verb")) {
16458                if (cfg.verb instanceof TinCan.Verb) {
16459                    this.verb = cfg.verb;
16460                } else {
16461                    this.verb = new TinCan.Verb (cfg.verb);
16462                }
16463            }
16464            if (cfg.hasOwnProperty("target")) {
16465                if (cfg.target instanceof TinCan.Activity ||
16466                    cfg.target instanceof TinCan.Agent ||
16467                    cfg.target instanceof TinCan.Group ||
16468                    cfg.target instanceof TinCan.SubStatement ||
16469                    cfg.target instanceof TinCan.StatementRef
16470                ) {
16471                    this.target = cfg.target;
16472                } else {
16473                    if (typeof cfg.target.objectType === "undefined") {
16474                        cfg.target.objectType = "Activity";
16475                    }
16476
16477                    if (cfg.target.objectType === "Activity") {
16478                        this.target = new TinCan.Activity (cfg.target);
16479                    } else if (cfg.target.objectType === "Agent") {
16480                        this.target = new TinCan.Agent (cfg.target);
16481                    } else if (cfg.target.objectType === "Group") {
16482                        this.target = new TinCan.Group (cfg.target);
16483                    } else if (cfg.target.objectType === "SubStatement") {
16484                        this.target = new TinCan.SubStatement (cfg.target);
16485                    } else if (cfg.target.objectType === "StatementRef") {
16486                        this.target = new TinCan.StatementRef (cfg.target);
16487                    } else {
16488                        this.log("Unrecognized target type: " + cfg.target.objectType);
16489                    }
16490                }
16491            }
16492            if (cfg.hasOwnProperty("result")) {
16493                if (cfg.result instanceof TinCan.Result) {
16494                    this.result = cfg.result;
16495                } else {
16496                    this.result = new TinCan.Result (cfg.result);
16497                }
16498            }
16499            if (cfg.hasOwnProperty("context")) {
16500                if (cfg.context instanceof TinCan.Context) {
16501                    this.context = cfg.context;
16502                } else {
16503                    this.context = new TinCan.Context (cfg.context);
16504                }
16505            }
16506            if (cfg.hasOwnProperty("attachments") && cfg.attachments !== null) {
16507                this.attachments = [];
16508                for (i = 0; i < cfg.attachments.length; i += 1) {
16509                    if (! (cfg.attachments[i] instanceof TinCan.Attachment)) {
16510                        this.attachments.push(new TinCan.Attachment (cfg.attachments[i]));
16511                    }
16512                    else {
16513                        this.attachments.push(cfg.attachments[i]);
16514                    }
16515                }
16516            }
16517
16518            for (i = 0; i < directProps.length; i += 1) {
16519                if (cfg.hasOwnProperty(directProps[i]) && cfg[directProps[i]] !== null) {
16520                    this[directProps[i]] = cfg[directProps[i]];
16521                }
16522            }
16523
16524            if (initCfg.doStamp) {
16525                this.stamp();
16526            }
16527        },
16528
16529        /**
16530        @method toString
16531        @return {String} String representation of the statement
16532        */
16533        toString: function (lang) {
16534            this.log("toString");
16535            return (this.actor !== null ? this.actor.toString(lang) : "") +
16536                    " " +
16537                    (this.verb !== null ? this.verb.toString(lang) : "") +
16538                    " " +
16539                    (this.target !== null ? this.target.toString(lang) : "");
16540        },
16541
16542        /**
16543        @method asVersion
16544        @param {String} [version] Version to return (defaults to newest supported)
16545        */
16546        asVersion: function (version) {
16547            this.log("asVersion");
16548            var result = {},
16549                optionalDirectProps = [
16550                    "id",
16551                    "timestamp"
16552                ],
16553                optionalObjProps = [
16554                    "actor",
16555                    "verb",
16556                    "result",
16557                    "context",
16558                    "authority"
16559                ],
16560                i;
16561
16562            version = version || TinCan.versions()[0];
16563
16564            for (i = 0; i < optionalDirectProps.length; i += 1) {
16565                if (this[optionalDirectProps[i]] !== null) {
16566                    result[optionalDirectProps[i]] = this[optionalDirectProps[i]];
16567                }
16568            }
16569            for (i = 0; i < optionalObjProps.length; i += 1) {
16570                if (this[optionalObjProps[i]] !== null) {
16571                    result[optionalObjProps[i]] = this[optionalObjProps[i]].asVersion(version);
16572                }
16573            }
16574            if (this.target !== null) {
16575                result.object = this.target.asVersion(version);
16576            }
16577
16578            if (version === "0.9" || version === "0.95") {
16579                if (this.voided !== null) {
16580                    result.voided = this.voided;
16581                }
16582            }
16583            if (version === "0.9" && this.inProgress !== null) {
16584                result.inProgress = this.inProgress;
16585            }
16586            if (this.attachments !== null) {
16587                if (! (version === "0.9" || version === "0.95")) {
16588                    result.attachments = [];
16589                    for (i = 0; i < this.attachments.length; i += 1) {
16590                        if (this.attachments[i] instanceof TinCan.Attachment) {
16591                            result.attachments.push(this.attachments[i].asVersion(version));
16592                        }
16593                        else {
16594                            result.attachments.push(new TinCan.Attachment(this.attachments[i]).asVersion(version));
16595                        }
16596                    }
16597                }
16598            }
16599
16600            return result;
16601        },
16602
16603        /**
16604        Sets 'id' and 'timestamp' properties if not already set
16605
16606        @method stamp
16607        */
16608        stamp: function () {
16609            this.log("stamp");
16610            if (this.id === null) {
16611                this.id = TinCan.Utils.getUUID();
16612            }
16613            if (this.timestamp === null) {
16614                this.timestamp = TinCan.Utils.getISODateString(new Date());
16615            }
16616        },
16617
16618        /**
16619        Checks if the Statement has at least one attachment with content
16620
16621        @method hasAttachmentsWithContent
16622        */
16623        hasAttachmentWithContent: function () {
16624            this.log("hasAttachmentWithContent");
16625            var i;
16626
16627            if (this.attachments === null) {
16628                return false;
16629            }
16630
16631            for (i = 0; i < this.attachments.length; i += 1) {
16632                if (this.attachments[i].content !== null) {
16633                    return true;
16634                }
16635            }
16636
16637            return false;
16638        }
16639    };
16640
16641    /**
16642    @method fromJSON
16643    @return {Object} Statement
16644    @static
16645    */
16646    Statement.fromJSON = function (stJSON) {
16647        Statement.prototype.log("fromJSON");
16648        var _st = JSON.parse(stJSON);
16649
16650        return new Statement(_st);
16651    };
16652}());
16653
16654/*
16655    Copyright 2012 Rustici Software
16656
16657    Licensed under the Apache License, Version 2.0 (the "License");
16658    you may not use this file except in compliance with the License.
16659    You may obtain a copy of the License at
16660
16661    http://www.apache.org/licenses/LICENSE-2.0
16662
16663    Unless required by applicable law or agreed to in writing, software
16664    distributed under the License is distributed on an "AS IS" BASIS,
16665    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16666    See the License for the specific language governing permissions and
16667    limitations under the License.
16668*/
16669
16670/**
16671TinCan client library
16672
16673@module TinCan
16674@submodule TinCan.StatementsResult
16675**/
16676(function () {
16677    "use strict";
16678
16679    /**
16680    @class TinCan.StatementsResult
16681    @constructor
16682    @param {Object} options Configuration used to initialize.
16683        @param {Array} options.statements Actor of statement
16684        @param {String} options.more URL to fetch more data
16685    **/
16686    var StatementsResult = TinCan.StatementsResult = function (cfg) {
16687        this.log("constructor");
16688
16689        /**
16690        @property statements
16691        @type Array
16692        */
16693        this.statements = null;
16694
16695        /**
16696        @property more
16697        @type String
16698        */
16699        this.more = null;
16700
16701        this.init(cfg);
16702    };
16703
16704    StatementsResult.prototype = {
16705        /**
16706        @property LOG_SRC
16707        */
16708        LOG_SRC: "StatementsResult",
16709
16710        /**
16711        @method log
16712        */
16713        log: TinCan.prototype.log,
16714
16715        /**
16716        @method init
16717        @param {Object} [options] Configuration used to initialize
16718        */
16719        init: function (cfg) {
16720            this.log("init");
16721
16722            cfg = cfg || {};
16723
16724            if (cfg.hasOwnProperty("statements")) {
16725                this.statements = cfg.statements;
16726            }
16727            if (cfg.hasOwnProperty("more")) {
16728                this.more = cfg.more;
16729            }
16730        }
16731    };
16732
16733    /**
16734    @method fromJSON
16735    @return {Object} Agent
16736    @static
16737    */
16738    StatementsResult.fromJSON = function (resultJSON) {
16739        StatementsResult.prototype.log("fromJSON");
16740        var _result,
16741            stmts = [],
16742            stmt,
16743            i
16744        ;
16745
16746        try {
16747            _result = JSON.parse(resultJSON);
16748        } catch (parseError) {
16749            StatementsResult.prototype.log("fromJSON - JSON.parse error: " + parseError);
16750        }
16751
16752        if (_result) {
16753            for (i = 0; i < _result.statements.length; i += 1) {
16754                try {
16755                    stmt = new TinCan.Statement (_result.statements[i], 4);
16756                } catch (error) {
16757                    StatementsResult.prototype.log("fromJSON - statement instantiation failed: " + error + " (" + JSON.stringify(_result.statements[i]) + ")");
16758
16759                    stmt = new TinCan.Statement (
16760                        {
16761                            id: _result.statements[i].id
16762                        },
16763                        4
16764                    );
16765                }
16766
16767                stmts.push(stmt);
16768            }
16769            _result.statements = stmts;
16770        }
16771
16772        return new StatementsResult (_result);
16773    };
16774}());
16775
16776/*
16777    Copyright 2012 Rustici Software
16778
16779    Licensed under the Apache License, Version 2.0 (the "License");
16780    you may not use this file except in compliance with the License.
16781    You may obtain a copy of the License at
16782
16783    http://www.apache.org/licenses/LICENSE-2.0
16784
16785    Unless required by applicable law or agreed to in writing, software
16786    distributed under the License is distributed on an "AS IS" BASIS,
16787    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16788    See the License for the specific language governing permissions and
16789    limitations under the License.
16790*/
16791
16792/**
16793TinCan client library
16794
16795@module TinCan
16796@submodule TinCan.State
16797**/
16798(function () {
16799    "use strict";
16800
16801    /**
16802    @class TinCan.State
16803    @constructor
16804    */
16805    var State = TinCan.State = function (cfg) {
16806        this.log("constructor");
16807
16808        /**
16809        @property id
16810        @type String
16811        */
16812        this.id = null;
16813
16814        /**
16815        @property updated
16816        @type Boolean
16817        */
16818        this.updated = null;
16819
16820        /**
16821        @property contents
16822        @type String
16823        */
16824        this.contents = null;
16825
16826        /**
16827        @property etag
16828        @type String
16829        */
16830        this.etag = null;
16831
16832        /**
16833        @property contentType
16834        @type String
16835        */
16836        this.contentType = null;
16837
16838        this.init(cfg);
16839    };
16840    State.prototype = {
16841        /**
16842        @property LOG_SRC
16843        */
16844        LOG_SRC: "State",
16845
16846        /**
16847        @method log
16848        */
16849        log: TinCan.prototype.log,
16850
16851        /**
16852        @method init
16853        @param {Object} [options] Configuration used to initialize
16854        */
16855        init: function (cfg) {
16856            this.log("init");
16857            var i,
16858                directProps = [
16859                    "id",
16860                    "contents",
16861                    "etag",
16862                    "contentType"
16863                ];
16864
16865            cfg = cfg || {};
16866
16867            for (i = 0; i < directProps.length; i += 1) {
16868                if (cfg.hasOwnProperty(directProps[i]) && cfg[directProps[i]] !== null) {
16869                    this[directProps[i]] = cfg[directProps[i]];
16870                }
16871            }
16872
16873            this.updated = false;
16874        }
16875    };
16876
16877    /**
16878    @method fromJSON
16879    @return {Object} State
16880    @static
16881    */
16882    State.fromJSON = function (stateJSON) {
16883        State.prototype.log("fromJSON");
16884        var _state = JSON.parse(stateJSON);
16885
16886        return new State(_state);
16887    };
16888}());
16889
16890/*
16891    Copyright 2012 Rustici Software
16892
16893    Licensed under the Apache License, Version 2.0 (the "License");
16894    you may not use this file except in compliance with the License.
16895    You may obtain a copy of the License at
16896
16897    http://www.apache.org/licenses/LICENSE-2.0
16898
16899    Unless required by applicable law or agreed to in writing, software
16900    distributed under the License is distributed on an "AS IS" BASIS,
16901    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16902    See the License for the specific language governing permissions and
16903    limitations under the License.
16904*/
16905
16906/**
16907TinCan client library
16908
16909@module TinCan
16910@submodule TinCan.ActivityProfile
16911**/
16912(function () {
16913    "use strict";
16914
16915    /**
16916    @class TinCan.ActivityProfile
16917    @constructor
16918    */
16919    var ActivityProfile = TinCan.ActivityProfile = function (cfg) {
16920        this.log("constructor");
16921
16922        /**
16923        @property id
16924        @type String
16925        */
16926        this.id = null;
16927
16928        /**
16929        @property activity
16930        @type TinCan.Activity
16931        */
16932        this.activity = null;
16933
16934        /**
16935        @property updated
16936        @type String
16937        */
16938        this.updated = null;
16939
16940        /**
16941        @property contents
16942        @type String
16943        */
16944        this.contents = null;
16945
16946        /**
16947        SHA1 of contents as provided by the server during last fetch,
16948        this should be passed through to saveActivityProfile
16949
16950        @property etag
16951        @type String
16952        */
16953        this.etag = null;
16954
16955        /**
16956        @property contentType
16957        @type String
16958        */
16959        this.contentType = null;
16960
16961        this.init(cfg);
16962    };
16963    ActivityProfile.prototype = {
16964        /**
16965        @property LOG_SRC
16966        */
16967        LOG_SRC: "ActivityProfile",
16968
16969        /**
16970        @method log
16971        */
16972        log: TinCan.prototype.log,
16973
16974        /**
16975        @method init
16976        @param {Object} [options] Configuration used to initialize
16977        */
16978        init: function (cfg) {
16979            this.log("init");
16980            var i,
16981                directProps = [
16982                    "id",
16983                    "contents",
16984                    "etag",
16985                    "contentType"
16986                ];
16987
16988            cfg = cfg || {};
16989
16990            if (cfg.hasOwnProperty("activity")) {
16991                if (cfg.activity instanceof TinCan.Activity) {
16992                    this.activity = cfg.activity;
16993                }
16994                else {
16995                    this.activity = new TinCan.Activity (cfg.activity);
16996                }
16997            }
16998
16999            for (i = 0; i < directProps.length; i += 1) {
17000                if (cfg.hasOwnProperty(directProps[i]) && cfg[directProps[i]] !== null) {
17001                    this[directProps[i]] = cfg[directProps[i]];
17002                }
17003            }
17004
17005            this.updated = false;
17006        }
17007    };
17008
17009    /**
17010    @method fromJSON
17011    @return {Object} ActivityProfile
17012    @static
17013    */
17014    ActivityProfile.fromJSON = function (stateJSON) {
17015        ActivityProfile.prototype.log("fromJSON");
17016        var _state = JSON.parse(stateJSON);
17017
17018        return new ActivityProfile(_state);
17019    };
17020}());
17021
17022/*
17023    Copyright 2013 Rustici Software
17024
17025    Licensed under the Apache License, Version 2.0 (the "License");
17026    you may not use this file except in compliance with the License.
17027    You may obtain a copy of the License at
17028
17029    http://www.apache.org/licenses/LICENSE-2.0
17030
17031    Unless required by applicable law or agreed to in writing, software
17032    distributed under the License is distributed on an "AS IS" BASIS,
17033    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17034    See the License for the specific language governing permissions and
17035    limitations under the License.
17036*/
17037
17038/**
17039TinCan client library
17040
17041@module TinCan
17042@submodule TinCan.AgentProfile
17043**/
17044(function () {
17045    "use strict";
17046
17047    /**
17048    @class TinCan.AgentProfile
17049    @constructor
17050    */
17051    var AgentProfile = TinCan.AgentProfile = function (cfg) {
17052        this.log("constructor");
17053
17054        /**
17055        @property id
17056        @type String
17057        */
17058        this.id = null;
17059
17060        /**
17061        @property agent
17062        @type TinCan.Agent
17063        */
17064        this.agent = null;
17065
17066        /**
17067        @property updated
17068        @type String
17069        */
17070        this.updated = null;
17071
17072        /**
17073        @property contents
17074        @type String
17075        */
17076        this.contents = null;
17077
17078        /**
17079        SHA1 of contents as provided by the server during last fetch,
17080        this should be passed through to saveAgentProfile
17081
17082        @property etag
17083        @type String
17084        */
17085        this.etag = null;
17086
17087        /**
17088        @property contentType
17089        @type String
17090        */
17091        this.contentType = null;
17092
17093        this.init(cfg);
17094    };
17095    AgentProfile.prototype = {
17096        /**
17097        @property LOG_SRC
17098        */
17099        LOG_SRC: "AgentProfile",
17100
17101        /**
17102        @method log
17103        */
17104        log: TinCan.prototype.log,
17105
17106        /**
17107        @method init
17108        @param {Object} [options] Configuration used to initialize
17109        */
17110        init: function (cfg) {
17111            this.log("init");
17112            var i,
17113                directProps = [
17114                    "id",
17115                    "contents",
17116                    "etag",
17117                    "contentType"
17118                ];
17119
17120            cfg = cfg || {};
17121
17122            if (cfg.hasOwnProperty("agent")) {
17123                if (cfg.agent instanceof TinCan.Agent) {
17124                    this.agent = cfg.agent;
17125                }
17126                else {
17127                    this.agent = new TinCan.Agent (cfg.agent);
17128                }
17129            }
17130
17131            for (i = 0; i < directProps.length; i += 1) {
17132                if (cfg.hasOwnProperty(directProps[i]) && cfg[directProps[i]] !== null) {
17133                    this[directProps[i]] = cfg[directProps[i]];
17134                }
17135            }
17136
17137            this.updated = false;
17138        }
17139    };
17140
17141    /**
17142    @method fromJSON
17143    @return {Object} AgentProfile
17144    @static
17145    */
17146    AgentProfile.fromJSON = function (stateJSON) {
17147        AgentProfile.prototype.log("fromJSON");
17148        var _state = JSON.parse(stateJSON);
17149
17150        return new AgentProfile(_state);
17151    };
17152}());
17153
17154/*
17155    Copyright 2014 Rustici Software
17156
17157    Licensed under the Apache License, Version 2.0 (the "License");
17158    you may not use this file except in compliance with the License.
17159    You may obtain a copy of the License at
17160
17161    http://www.apache.org/licenses/LICENSE-2.0
17162
17163    Unless required by applicable law or agreed to in writing, software
17164    distributed under the License is distributed on an "AS IS" BASIS,
17165    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17166    See the License for the specific language governing permissions and
17167    limitations under the License.
17168*/
17169
17170/**
17171TinCan client library
17172
17173@module TinCan
17174@submodule TinCan.About
17175**/
17176(function () {
17177    "use strict";
17178
17179    /**
17180    @class TinCan.About
17181    @constructor
17182    */
17183    var About = TinCan.About = function (cfg) {
17184        this.log("constructor");
17185
17186        /**
17187        @property version
17188        @type {String[]}
17189        */
17190        this.version = null;
17191
17192        this.init(cfg);
17193    };
17194    About.prototype = {
17195        /**
17196        @property LOG_SRC
17197        */
17198        LOG_SRC: "About",
17199
17200        /**
17201        @method log
17202        */
17203        log: TinCan.prototype.log,
17204
17205        /**
17206        @method init
17207        @param {Object} [options] Configuration used to initialize
17208        */
17209        init: function (cfg) {
17210            this.log("init");
17211            var i,
17212                directProps = [
17213                    "version"
17214                ];
17215
17216            cfg = cfg || {};
17217
17218            for (i = 0; i < directProps.length; i += 1) {
17219                if (cfg.hasOwnProperty(directProps[i]) && cfg[directProps[i]] !== null) {
17220                    this[directProps[i]] = cfg[directProps[i]];
17221                }
17222            }
17223        }
17224    };
17225
17226    /**
17227    @method fromJSON
17228    @return {Object} About
17229    @static
17230    */
17231    About.fromJSON = function (aboutJSON) {
17232        About.prototype.log("fromJSON");
17233        var _about = JSON.parse(aboutJSON);
17234
17235        return new About(_about);
17236    };
17237}());
17238
17239/*
17240    Copyright 2016 Rustici Software
17241
17242    Licensed under the Apache License, Version 2.0 (the "License");
17243    you may not use this file except in compliance with the License.
17244    You may obtain a copy of the License at
17245
17246    http://www.apache.org/licenses/LICENSE-2.0
17247
17248    Unless required by applicable law or agreed to in writing, software
17249    distributed under the License is distributed on an "AS IS" BASIS,
17250    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17251    See the License for the specific language governing permissions and
17252    limitations under the License.
17253*/
17254
17255/**
17256TinCan client library
17257
17258@module TinCan
17259@submodule TinCan.Attachment
17260**/
17261(function () {
17262    "use strict";
17263
17264    /**
17265    @class TinCan.Attachment
17266    @constructor
17267    */
17268    var Attachment = TinCan.Attachment = function (cfg) {
17269        this.log("constructor");
17270
17271        /**
17272        @property usageType
17273        @type String
17274        */
17275        this.usageType = null;
17276
17277        /**
17278        @property display
17279        @type Object
17280        */
17281        this.display = null;
17282
17283        /**
17284        @property contentType
17285        @type String
17286        */
17287        this.contentType = null;
17288
17289        /**
17290        @property length
17291        @type int
17292        */
17293        this.length = null;
17294
17295        /**
17296        @property sha2
17297        @type String
17298        */
17299        this.sha2 = null;
17300
17301        /**
17302        @property description
17303        @type Object
17304        */
17305        this.description = null;
17306
17307        /**
17308        @property fileUrl
17309        @type String
17310        */
17311        this.fileUrl = null;
17312
17313        /**
17314        @property content
17315        @type ArrayBuffer
17316        */
17317        this.content = null;
17318
17319        this.init(cfg);
17320    };
17321    Attachment.prototype = {
17322        /**
17323        @property LOG_SRC
17324        */
17325        LOG_SRC: "Attachment",
17326
17327        /**
17328        @method log
17329        */
17330        log: TinCan.prototype.log,
17331
17332        /**
17333        @method init
17334        @param {Object} [options] Configuration used to initialize
17335        */
17336        init: function (cfg) {
17337            this.log("init");
17338            var i,
17339                directProps = [
17340                    "contentType",
17341                    "length",
17342                    "sha2",
17343                    "usageType",
17344                    "display",
17345                    "description",
17346                    "fileUrl"
17347                ]
17348            ;
17349
17350            cfg = cfg || {};
17351
17352            for (i = 0; i < directProps.length; i += 1) {
17353                if (cfg.hasOwnProperty(directProps[i]) && cfg[directProps[i]] !== null) {
17354                    this[directProps[i]] = cfg[directProps[i]];
17355                }
17356            }
17357
17358            if (cfg.hasOwnProperty("content") && cfg.content !== null) {
17359                if (typeof cfg.content === "string") {
17360                    this.setContentFromString(cfg.content);
17361                }
17362                else {
17363                    this.setContent(cfg.content);
17364                }
17365            }
17366        },
17367
17368        /**
17369        @method asVersion
17370        @param {String} [version] Version to return (defaults to newest supported)
17371        */
17372        asVersion: function (version) {
17373            this.log("asVersion");
17374            var result;
17375
17376            version = version || TinCan.versions()[0];
17377
17378            if (version === "0.9" || version === "0.95") {
17379                result = null;
17380            }
17381            else {
17382                result = {
17383                    contentType: this.contentType,
17384                    display: this.display,
17385                    length: this.length,
17386                    sha2: this.sha2,
17387                    usageType: this.usageType
17388                };
17389
17390                if (this.fileUrl !== null) {
17391                    result.fileUrl = this.fileUrl;
17392                }
17393                if (this.description !== null) {
17394                    result.description = this.description;
17395                }
17396            }
17397
17398            return result;
17399        },
17400
17401        /**
17402        See {{#crossLink "TinCan.Utils/getLangDictionaryValue"}}{{/crossLink}}
17403
17404        @method getLangDictionaryValue
17405        */
17406        getLangDictionaryValue: TinCan.Utils.getLangDictionaryValue,
17407
17408        /**
17409        @method setContent
17410        @param {ArrayBuffer} content Sets content from ArrayBuffer
17411        */
17412        setContent: function (content) {
17413            this.content = content;
17414            this.length = content.byteLength;
17415            this.sha2 = TinCan.Utils.getSHA256String(content);
17416        },
17417
17418        /**
17419        @method setContentFromString
17420        @param {String} content Sets the content property of the attachment from a string
17421        */
17422        setContentFromString: function (content) {
17423            var _content = content;
17424
17425            _content = TinCan.Utils.stringToArrayBuffer(content);
17426
17427            this.setContent(_content);
17428        },
17429
17430        /**
17431        @method getContentAsString
17432        @return {String}
17432 Value of content property as a string
17433        */
17434        getContentAsString: function () {
17435            return TinCan.Utils.stringFromArrayBuffer(this.content);
17436        }
17437    };
17438
17439    /**
17440    @method fromJSON
17441    @return {Object} Attachment
17442    @static
17443    */
17444    Attachment.fromJSON = function (attachmentJSON) {
17445        Attachment.prototype.log("fromJSON");
17446        var _attachment = JSON.parse(attachmentJSON);
17447
17448        return new Attachment(_attachment);
17449    };
17450
17451    Attachment._defaultEncoding = "utf-8";
17452}());
17453
17454/*
17455    Copyright 2012-2013 Rustici Software
17456
17457    Licensed under the Apache License, Version 2.0 (the "License");
17458    you may not use this file except in compliance with the License.
17459    You may obtain a copy of the License at
17460
17461    http://www.apache.org/licenses/LICENSE-2.0
17462
17463    Unless required by applicable law or agreed to in writing, software
17464    distributed under the License is distributed on an "AS IS" BASIS,
17465    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17466    See the License for the specific language governing permissions and
17467    limitations under the License.
17468*/
17469
17470/**
17471TinCan client library
17472
17473@module TinCan
17474@submodule TinCan.Environment.Browser
17475**/
17476(function () {
17477    /* globals window, XMLHttpRequest, XDomainRequest, Blob */
17478    "use strict";
17479    var LOG_SRC = "Environment.Browser",
17480        requestComplete,
17481        __IEModeConversion,
17482        nativeRequest,
17483        xdrRequest,
17484        __createJSONSegment,
17485        __createAttachmentSegment,
17486        __delay,
17487        env = {},
17488        log = TinCan.prototype.log;
17489
17490    if (typeof window === "undefined") {
17491        log("'window' not defined", LOG_SRC);
17492        return;
17493    }
17494
17495    /* Shims for browsers not supporting our needs, mainly IE */
17496
17497    //
17498    // Make Date.now safe for IE < 9
17499    // https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Date/now
17500    //
17501    if (!Date.now) {
17502        Date.now = function () {
17503            return +(new Date ());
17504        };
17505    }
17506
17507    //
17508    // Add .forEach implementation for supporting our string encoding polyfill
17509    // imported from js-polyfills to avoid bringing in the whole es5 shim
17510    // for now, a rewrite probably moves all shims out of the main build or at
17511    // least Environment file and leverages more of them
17512    //
17513
17514    // ES5 15.4.4.18 Array.prototype.forEach ( callbackfn [ , thisArg ] )
17515    // From https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/forEach
17516    if (!Array.prototype.forEach) {
17517      /* jshint freeze:false,bitwise:false */
17518      Array.prototype.forEach = function (fun /*, thisp */) {
17519        if (this === void 0 || this === null) { throw new TypeError(); }
17520
17521        var t = Object(this);
17522        var len = t.length >>> 0;
17523        if (typeof fun !== "function") { throw new TypeError(); }
17524
17525        var thisp = arguments[1], i;
17526        for (i = 0; i < len; i += 1) {
17527          if (i in t) {
17528            fun.call(thisp, t[i], i, t);
17529          }
17530        }
17531      };
17532    }
17533
17534    /* Detect CORS and XDR support */
17535    env.hasCORS = false;
17536    env.useXDR = false;
17537
17538    if (typeof XMLHttpRequest !== "undefined" && typeof (new XMLHttpRequest()).withCredentials !== "undefined") {
17539        env.hasCORS = true;
17540    }
17541    else if (typeof XDomainRequest !== "undefined") {
17542        env.hasCORS = true;
17543        env.useXDR = true;
17544    }
17545
17546    // TODO: should we have our own internal "Request" object
17547    //       that replaces the need for "control"?
17548
17549    //
17550    // Setup request callback
17551    //
17552    requestComplete = function (xhr, cfg, control) {
17553        log("requestComplete: " + control.finished + ", xhr.status: " + xhr.status, LOG_SRC);
17554        var requestCompleteResult,
17555            notFoundOk,
17556            httpStatus;
17557
17558        //
17559        // XDomainRequest doesn't give us a way to get the status,
17560        // so allow passing in a forged one
17561        //
17562        if (typeof xhr.status === "undefined") {
17563            httpStatus = control.fakeStatus;
17564        }
17565        else {
17566            //
17567            // older versions of IE don't properly handle 204 status codes
17568            // so correct when receiving a 1223 to be 204 locally
17569            // http://stackoverflow.com/questions/10046972/msie-returns-status-code-of-1223-for-ajax-request
17570            //
17571            httpStatus = (xhr.status === 1223) ? 204 : xhr.status;
17572        }
17573
17574        if (! control.finished) {
17575            // may be in sync or async mode, using XMLHttpRequest or IE XDomainRequest, onreadystatechange or
17576            // onload or both might fire depending upon browser, just covering all bases with event hooks and
17577            // using 'finished' flag to avoid triggering events multiple times
17578            control.finished = true;
17579
17580            notFoundOk = (cfg.ignore404 && httpStatus === 404);
17581            if ((httpStatus >= 200 && httpStatus < 400) || notFoundOk) {
17582                if (cfg.callback) {
17583                    cfg.callback(null, xhr);
17584                }
17585                else {
17586                    requestCompleteResult = {
17587                        err: null,
17588                        xhr: xhr
17589                    };
17590                    return requestCompleteResult;
17591                }
17592            }
17593            else {
17594                requestCompleteResult = {
17595                    err: httpStatus,
17596                    xhr: xhr
17597                };
17598                if (httpStatus === 0) {
17599                    log("[warning] There was a problem communicating with the Learning Record Store. Aborted, offline, or invalid CORS endpoint (" + httpStatus + ")", LOG_SRC);
17600                }
17601                else {
17602                    log("[warning] There was a problem communicating with the Learning Record Store. (" + httpStatus + " | " + xhr.responseText+ ")", LOG_SRC);
17603                }
17604                if (cfg.callback) {
17605                    cfg.callback(httpStatus, xhr);
17606                }
17607                return requestCompleteResult;
17608            }
17609        }
17610        else {
17611            return requestCompleteResult;
17612        }
17613    };
17614
17615    //
17616    // Converts an HTTP request cfg of above a set length (//MAX_REQUEST_LENGTH) to a post
17617    // request cfg, with the original request as the form data.
17618    //
17619    __IEModeConversion = function (fullUrl, headers, pairs, cfg) {
17620        var prop;
17621
17622        // 'pairs' already holds the original cfg params, now needs headers and data
17623        // from the original cfg to add as the form data to the POST request
17624        for (prop in headers) {
17625            if (headers.hasOwnProperty(prop)) {
17626                pairs.push(prop + "=" + encodeURIComponent(headers[prop]));
17627            }
17628        }
17629
17630        if (typeof cfg.data !== "undefined") {
17631            pairs.push("content=" + encodeURIComponent(cfg.data));
17632        }
17633
17634        // the Authorization and xAPI version headers need to still be present, but
17635        // the content type must exist and be of type application/x-www-form-urlencoded
17636        headers["Content-Type"] = "application/x-www-form-urlencoded";
17637        fullUrl += "?method=" + cfg.method;
17638        cfg.method = "POST";
17639        cfg.params = {};
17640        if (pairs.length > 0) {
17641            cfg.data = pairs.join("&");
17642        }
17643        return fullUrl;
17644    };
17645
17646    //
17647    // one of the two of these is stuffed into the LRS' instance
17648    // as ._makeRequest
17649    //
17650    nativeRequest = function (fullUrl, headers, cfg) {
17651        /*global ActiveXObject*/
17652        log("sendRequest using XMLHttpRequest", LOG_SRC);
17653        var self = this,
17654            xhr,
17655            prop,
17656            pairs = [],
17657            data,
17658            control = {
17659                finished: false,
17660                fakeStatus: null
17661            },
17662            async = typeof cfg.callback !== "undefined",
17663            fullRequest = fullUrl,
17664            err,
17665            MAX_REQUEST_LENGTH = 2048
17666        ;
17667        log("sendRequest using XMLHttpRequest - async: " + async, LOG_SRC);
17668
17669        for (prop in cfg.params) {
17670            if (cfg.params.hasOwnProperty(prop)) {
17671                pairs.push(prop + "=" + encodeURIComponent(cfg.params[prop]));
17672            }
17673        }
17674
17675        if (pairs.length > 0) {
17676            fullRequest += "?" + pairs.join("&");
17677        }
17678
17679        if (fullRequest.length >= MAX_REQUEST_LENGTH) {
17680            if (typeof cfg.method === "undefined") {
17681                err = new Error("method must not be undefined for an IE Mode Request conversion");
17682                if (typeof cfg.callback !== "undefined") {
17683                    cfg.callback(err, null);
17684                }
17685                return {
17686                    err: err,
17687                    xhr: null
17688                };
17689            }
17690
17691            fullUrl = __IEModeConversion(fullUrl, headers, pairs, cfg);
17692        }
17693        else {
17694            fullUrl = fullRequest;
17695        }
17696
17697        if (typeof XMLHttpRequest !== "undefined") {
17698            xhr = new XMLHttpRequest();
17699        }
17700        else {
17701            //
17702            // IE6 implements XMLHttpRequest through ActiveX control
17703            // http://blogs.msdn.com/b/ie/archive/2006/01/23/516393.aspx
17704            //
17705            xhr = new ActiveXObject("Microsoft.XMLHTTP");
17706
17707            if (cfg.expectMultipart) {
17708                err = new Error("Attachment support not available");
17709                if (typeof cfg.callback !== "undefined") {
17710                    cfg.callback(err, null);
17711                }
17712                return {
17713                    err: err,
17714                    xhr: null
17715                };
17716            }
17717        }
17718
17719        xhr.open(cfg.method, fullUrl, async);
17720
17721        //
17722        // setting the .responseType before .open was causing IE to fail
17723        // with a StateError, so moved it to here
17724        //
17725        if (cfg.expectMultipart) {
17726            xhr.responseType = "arraybuffer";
17727        }
17728
17729        for (prop in headers) {
17730            if (headers.hasOwnProperty(prop)) {
17731                xhr.setRequestHeader(prop, headers[prop]);
17732            }
17733        }
17734
17735        data = cfg.data;
17736
17737        if (async) {
17738            xhr.onreadystatechange = function () {
17739                log("xhr.onreadystatechange - xhr.readyState: " + xhr.readyState, LOG_SRC);
17740                if (xhr.readyState === 4) {
17741                    requestComplete.call(self, xhr, cfg, control);
17742                }
17743            };
17744        }
17745
17746        //
17747        // research indicates that IE is known to just throw exceptions
17748        // on .send and it seems everyone pretty much just ignores them
17749        // including jQuery (https://github.com/jquery/jquery/blob/1.10.2/src/ajax.js#L549
17750        // https://github.com/jquery/jquery/blob/1.10.2/src/ajax/xhr.js#L97)
17751        //
17752        try {
17753            xhr.send(data);
17754        }
17755        catch (ex) {
17756            log("sendRequest caught send exception: " + ex, LOG_SRC);
17757        }
17758
17759        if (async) {
17760            //
17761            // for async requests give them the XHR object directly
17762            // as the return value, the actual stuff they should be
17763            // caring about is params to the callback, for sync
17764            // requests they got the return value above
17765            //
17766            return xhr;
17767        }
17768
17769        return requestComplete.call(this, xhr, cfg, control);
17770    };
17771    xdrRequest = function (fullUrl, headers, cfg) {
17772        log("sendRequest using XDomainRequest", LOG_SRC);
17773        var self = this,
17774            xhr,
17775            pairs = [],
17776            data,
17777            prop,
17778            until,
17779            control = {
17780                finished: false,
17781                fakeStatus: null
17782            },
17783            err;
17784
17785        if (cfg.expectMultipart) {
17786            err = new Error("Attachment support not available");
17787            if (typeof cfg.callback !== "undefined") {
17788                cfg.callback(err, null);
17789            }
17790            return {
17791                err: err,
17792                xhr: null
17793            };
17794        }
17795
17796        // method has to go on querystring, and nothing else,
17797        // and the actual method is then always POST
17798        fullUrl += "?method=" + cfg.method;
17799
17800        // params end up in the body
17801        for (prop in cfg.params) {
17802            if (cfg.params.hasOwnProperty(prop)) {
17803                pairs.push(prop + "=" + encodeURIComponent(cfg.params[prop]));
17804            }
17805        }
17806
17807        // headers go into form data
17808        for (prop in headers) {
17809            if (headers.hasOwnProperty(prop)) {
17810                pairs.push(prop + "=" + encodeURIComponent(headers[prop]));
17811            }
17812        }
17813
17814        // the original data is repackaged as "content" form var
17815        if (typeof cfg.data !== "undefined") {
17816            pairs.push("content=" + encodeURIComponent(cfg.data));
17817        }
17818
17819        data = pairs.join("&");
17820
17821        xhr = new XDomainRequest ();
17822        xhr.open("POST", fullUrl);
17823
17824        if (! cfg.callback) {
17825            xhr.onload = function () {
17826                control.fakeStatus = 200;
17827            };
17828            xhr.onerror = function () {
17829                control.fakeStatus = 400;
17830            };
17831            xhr.ontimeout = function () {
17832                control.fakeStatus = 0;
17833            };
17834        }
17835        else {
17836            xhr.onload = function () {
17837                control.fakeStatus = 200;
17838                requestComplete.call(self, xhr, cfg, control);
17839            };
17840            xhr.onerror = function () {
17841                control.fakeStatus = 400;
17842                requestComplete.call(self, xhr, cfg, control);
17843            };
17844            xhr.ontimeout = function () {
17845                control.fakeStatus = 0;
17846                requestComplete.call(self, xhr, cfg, control);
17847            };
17848        }
17849
17850        // IE likes to randomly abort requests when some handlers
17851        // aren't defined, so define them with no-ops, see:
17852        //
17853        // http://cypressnorth.com/programming/internet-explorer-aborting-ajax-requests-fixed/
17854        // http://social.msdn.microsoft.com/Forums/ie/en-US/30ef3add-767c-4436-b8a9-f1ca19b4812e/ie9-rtm-xdomainrequest-issued-requests-may-abort-if-all-event-handlers-not-specified
17855        //
17856        xhr.onprogress = function () {};
17857        xhr.timeout = 0;
17858
17859        //
17860        // research indicates that IE is known to just throw exceptions
17861        // on .send and it seems everyone pretty much just ignores them
17862        // including jQuery (https://github.com/jquery/jquery/blob/1.10.2/src/ajax.js#L549
17863        // https://github.com/jquery/jquery/blob/1.10.2/src/ajax/xhr.js#L97)
17864        //
17865        try {
17866            xhr.send(data);
17867        }
17868        catch (ex) {
17869            log("sendRequest caught send exception: " + ex, LOG_SRC);
17870        }
17871
17872        if (! cfg.callback) {
17873            // synchronous call in IE, with no synchronous mode available
17874            until = 10000 + Date.now();
17875            log("sendRequest - until: " + until + ", finished: " + control.finished, LOG_SRC);
17876
17877            while (Date.now() < until && control.fakeStatus === null) {
17878                //log("calling __delay", LOG_SRC);
17879                __delay();
17880            }
17881            return requestComplete.call(self, xhr, cfg, control);
17882        }
17883
17884        //
17885        // for async requests give them the XHR object directly
17886        // as the return value, the actual stuff they should be
17887        // caring about is params to the callback, for sync
17888        // requests they got the return value above
17889        //
17890        return xhr;
17891    };
17892
17893    //
17894    // Override LRS' init method to set up our request handling
17895    // capabilities
17896    //
17897    TinCan.LRS.prototype._initByEnvironment = function (cfg) {
17898        /*jslint regexp: true, laxbreak: true */
17899        /* globals location */
17900        log("_initByEnvironment", LOG_SRC);
17901        var urlParts,
17902            schemeMatches,
17903            locationPort,
17904            isXD
17905        ;
17906
17907        cfg = cfg || {};
17908
17909        //
17910        // default to native request mode
17911        //
17912        this._makeRequest = nativeRequest;
17913
17914        //
17915        // overload LRS ._IEModeConversion to be able to test this method,
17916        // which only applies in a browser setting
17917        //
17918        this._IEModeConversion = __IEModeConversion;
17919
17920        urlParts = this.endpoint.toLowerCase().match(/([A-Za-z]+:)\/\/([^:\/]+):?(\d+)?(\/.*)?$/);
17921        if (urlParts === null) {
17922            log("[error] LRS invalid: failed to divide URL parts", LOG_SRC);
17923            throw {
17924                code: 4,
17925                mesg: "LRS invalid: failed to divide URL parts"
17926            };
17927        }
17928
17929        //
17930        // determine whether this is a cross domain request,
17931        // whether our browser has CORS support at all, and then
17932        // if it does then if we are in IE with XDR only check that
17933        // the schemes match to see if we should be able to talk to
17934        // the LRS
17935        //
17936        locationPort = location.port;
17937        schemeMatches = location.protocol.toLowerCase() === urlParts[1];
17938
17939        //
17940        // normalize the location.port cause it appears to be "" when 80/443
17941        // but our endpoint may have provided it
17942        //
17943        if (locationPort === "") {
17944            locationPort = (location.protocol.toLowerCase() === "http:" ? "80" : (location.protocol.toLowerCase() === "https:" ? "443" : ""));
17945        }
17946
17947        isXD = (
17948            // is same scheme?
17949            ! schemeMatches
17950
17951            // is same host?
17952            || location.hostname.toLowerCase() !== urlParts[2]
17953
17954            // is same port?
17955            || locationPort !== (
17956                (urlParts[3] !== null && typeof urlParts[3] !== "undefined" && urlParts[3] !== "") ? urlParts[3] : (urlParts[1] === "http:" ? "80" : (urlParts[1] === "https:" ? "443" : ""))
17957            )
17958        );
17959        if (isXD) {
17960            if (env.hasCORS) {
17961                if (env.useXDR && schemeMatches) {
17962                    this._makeRequest = xdrRequest;
17963                }
17964                else if (env.useXDR && ! schemeMatches) {
17965                    if (cfg.allowFail) {
17966                        log("[warning] LRS invalid: cross domain request for differing scheme in IE with XDR (allowed to fail)", LOG_SRC);
17967                    }
17968                    else {
17969                        log("[error] LRS invalid: cross domain request for differing scheme in IE with XDR", LOG_SRC);
17970                        throw {
17971                            code: 2,
17972                            mesg: "LRS invalid: cross domain request for differing scheme in IE with XDR"
17973                        };
17974                    }
17975                }
17976            }
17977            else {
17978                if (cfg.allowFail) {
17979                    log("[warning] LRS invalid: cross domain requests not supported in this browser (allowed to fail)", LOG_SRC);
17980                }
17981                else {
17982                    log("[error] LRS invalid: cross domain requests not supported in this browser", LOG_SRC);
17983                    throw {
17984                        code: 1,
17985                        mesg: "LRS invalid: cross domain requests not supported in this browser"
17986                    };
17987                }
17988            }
17989        }
17990    };
17991
17992    /**
17993    Non-environment safe method used to create a delay to give impression
17994    of synchronous response (for IE, shocker)
17995
17996    @method __delay
17997    @private
17998    */
17999    __delay = function () {
18000        //
18001        // use a synchronous request to the current location to allow the browser
18002        // to yield to the asynchronous request's events but still block in the
18003        // outer loop to make it seem synchronous to the end user
18004        //
18005        // removing this made the while loop too tight to allow the asynchronous
18006        // events through to get handled so that the response was correctly handled
18007        //
18008        var xhr = new XMLHttpRequest (),
18009            url = window.location + "?forcenocache=" + TinCan.Utils.getUUID()
18010        ;
18011        xhr.open("GET", url, false);
18012        xhr.send(null);
18013    };
18014
18015    //
18016    // Synchronous xhr handling is accepted in the browser environment
18017    //
18018    TinCan.LRS.syncEnabled = true;
18019
18020    TinCan.LRS.prototype._getMultipartRequestData = function (boundary, jsonContent, requestAttachments) {
18021        var parts = [],
18022            i;
18023
18024        parts.push(
18025            __createJSONSegment(
18026                boundary,
18027                jsonContent
18028            )
18029        );
18030        for (i = 0; i < requestAttachments.length; i += 1) {
18031            if (requestAttachments[i].content !== null) {
18032                parts.push(
18033                    __createAttachmentSegment(
18034                        boundary,
18035                        requestAttachments[i].content,
18036                        requestAttachments[i].sha2,
18037                        requestAttachments[i].contentType
18038                    )
18039                );
18040            }
18041        }
18042        parts.push("\r\n--" + boundary + "--\r\n");
18043
18044        return new Blob(parts);
18045    };
18046
18047    __createJSONSegment = function (boundary, jsonContent) {
18048        var content = [
18049                "--" + boundary,
18050                "Content-Type: application/json",
18051                "",
18052                JSON.stringify(jsonContent)
18053            ].join("\r\n");
18054
18055        content += "\r\n";
18056
18057        return content;
18058    };
18059
18060    __createAttachmentSegment = function (boundary, content, sha2, contentType) {
18061        var blobParts = [],
18062            header = [
18063                "--" + boundary,
18064                "Content-Type: " + contentType,
18065                "Content-Transfer-Encoding: binary",
18066                "X-Experience-API-Hash: " + sha2
18067            ].join("\r\n");
18068
18069        header += "\r\n\r\n";
18070
18071        blobParts.push(header);
18072        blobParts.push(content);
18073
18074        return new Blob(blobParts);
18075    };
18076
18077    TinCan.Utils.stringToArrayBuffer = function (content, encoding) {
18078        /* global TextEncoder */
18079        var encoder;
18080
18081        if (! encoding) {
18082            encoding = TinCan.Utils.defaultEncoding;
18083        }
18084
18085        encoder = new TextEncoder(encoding);
18086
18087        return encoder.encode(content).buffer;
18088    };
18089
18090    TinCan.Utils.stringFromArrayBuffer = function (content, encoding) {
18091        /* global TextDecoder */
18092        var decoder;
18093
18094        if (! encoding) {
18095            encoding = TinCan.Utils.defaultEncoding;
18096        }
18097
18098        decoder = new TextDecoder(encoding);
18099
18100        return decoder.decode(content);
18101    };
18102}());
18103
vendor: 30,509 bytes, lines 18104-18951
18104/*
18105 Copyright (c) 2010, Linden Research, Inc.
18106 Copyright (c) 2014, Joshua Bell
18107
18108 Permission is hereby granted, free of charge, to any person obtaining a copy
18109 of this software and associated documentation files (the "Software"), to deal
18110 in the Software without restriction, including without limitation the rights
18111 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
18112 copies of the Software, and to permit persons to whom the Software is
18113 furnished to do so, subject to the following conditions:
18114
18115 The above copyright notice and this permission notice shall be included in
18116 all copies or substantial portions of the Software.
18117
18118 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18119 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18120 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18121 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18122 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18123 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
18124 THE SOFTWARE.
18125 $/LicenseInfo$
18126 */
18127
18128// Original can be found at:
18129//   https://bitbucket.org/lindenlab/llsd
18130// Modifications by Joshua Bell [email protected]
18131//   https://github.com/inexorabletash/polyfill
18132
18133// ES3/ES5 implementation of the Krhonos Typed Array Specification
18134//   Ref: http://www.khronos.org/registry/typedarray/specs/latest/
18135//   Date: 2011-02-01
18136//
18137// Variations:
18138//  * Allows typed_array.get/set() as alias for subscripts (typed_array[])
18139//  * Gradually migrating structure from Khronos spec to ES2015 spec
18140//
18141// Caveats:
18142//  * Beyond 10000 or so entries, polyfilled array accessors (ta[0],
18143//    etc) become memory-prohibitive, so array creation will fail. Set
18144//    self.TYPED_ARRAY_POLYFILL_NO_ARRAY_ACCESSORS=true to disable
18145//    creation of accessors. Your code will need to use the
18146//    non-standard get()/set() instead, and will need to add those to
18147//    native arrays for interop.
18148(function(global) {
18149  'use strict';
18150  var undefined = (void 0); // Paranoia
18151
18152  // Beyond this value, index getters/setters (i.e. array[0], array[1]) are so slow to
18153  // create, and consume so much memory, that the browser appears frozen.
18154  var MAX_ARRAY_LENGTH = 1e5;
18155
18156  // Approximations of internal ECMAScript conversion functions
18157  function Type(v) {
18158    switch(typeof v) {
18159    case 'undefined': return 'undefined';
18160    case 'boolean': return 'boolean';
18161    case 'number': return 'number';
18162    case 'string': return 'string';
18163    default: return v === null ? 'null' : 'object';
18164    }
18165  }
18166
18167  // Class returns internal [[Class]] property, used to avoid cross-frame instanceof issues:
18168  function Class(v) { return Object.prototype.toString.call(v).replace(/^\[object *|\]$/g, ''); }
18169  function IsCallable(o) { return typeof o === 'function'; }
18170  function ToObject(v) {
18171    if (v === null || v === undefined) throw TypeError();
18172    return Object(v);
18173  }
18174  function ToInt32(v) { return v >> 0; }
18175  function ToUint32(v) { return v >>> 0; }
18176
18177  // Snapshot intrinsics
18178  var LN2 = Math.LN2,
18179      abs = Math.abs,
18180      floor = Math.floor,
18181      log = Math.log,
18182      max = Math.max,
18183      min = Math.min,
18184      pow = Math.pow,
18185      round = Math.round;
18186
18187  // emulate ES5 getter/setter API using legacy APIs
18188  // http://blogs.msdn.com/b/ie/archive/2010/09/07/transitioning-existing-code-to-the-es5-getter-setter-apis.aspx
18189  // (second clause tests for Object.defineProperty() in IE<9 that only supports extending DOM prototypes, but
18190  // note that IE<9 does not support __defineGetter__ or __defineSetter__ so it just renders the method harmless)
18191
18192  (function() {
18193    var orig = Object.defineProperty;
18194    var dom_only = !(function(){try{return Object.defineProperty({},'x',{});}catch(_){return false;}}());
18195
18196    if (!orig || dom_only) {
18197      Object.defineProperty = function (o, prop, desc) {
18198        // In IE8 try built-in implementation for defining properties on DOM prototypes.
18199        if (orig)
18200          try { return orig(o, prop, desc); } catch (_) {}
18201        if (o !== Object(o))
18202          throw TypeError('Object.defineProperty called on non-object');
18203        if (Object.prototype.__defineGetter__ && ('get' in desc))
18204          Object.prototype.__defineGetter__.call(o, prop, desc.get);
18205        if (Object.prototype.__defineSetter__ && ('set' in desc))
18206          Object.prototype.__defineSetter__.call(o, prop, desc.set);
18207        if ('value' in desc)
18208          o[prop] = desc.value;
18209        return o;
18210      };
18211    }
18212  }());
18213
18214  // ES5: Make obj[index] an alias for obj._getter(index)/obj._setter(index, value)
18215  // for index in 0 ... obj.length
18216  function makeArrayAccessors(obj) {
18217    if ('TYPED_ARRAY_POLYFILL_NO_ARRAY_ACCESSORS' in global)
18218      return;
18219
18220    if (obj.length > MAX_ARRAY_LENGTH) throw RangeError('Array too large for polyfill');
18221
18222    function makeArrayAccessor(index) {
18223      Object.defineProperty(obj, index, {
18224        'get': function() { return obj._getter(index); },
18225        'set': function(v) { obj._setter(index, v); },
18226        enumerable: true,
18227        configurable: false
18228      });
18229    }
18230
18231    var i;
18232    for (i = 0; i < obj.length; i += 1) {
18233      makeArrayAccessor(i);
18234    }
18235  }
18236
18237  // Internal conversion functions:
18238  //    pack<Type>()   - take a number (interpreted as Type), output a byte array
18239  //    unpack<Type>() - take a byte array, output a Type-like number
18240
18241  function as_signed(value, bits) { var s = 32 - bits; return (value << s) >> s; }
18242  function as_unsigned(value, bits) { var s = 32 - bits; return (value << s) >>> s; }
18243
18244  function packI8(n) { return [n & 0xff]; }
18245  function unpackI8(bytes) { return as_signed(bytes[0], 8); }
18246
18247  function packU8(n) { return [n & 0xff]; }
18248  function unpackU8(bytes) { return as_unsigned(bytes[0], 8); }
18249
18250  function packU8Clamped(n) { n = round(Number(n)); return [n < 0 ? 0 : n > 0xff ? 0xff : n & 0xff]; }
18251
18252  function packI16(n) { return [n & 0xff, (n >> 8) & 0xff]; }
18253  function unpackI16(bytes) { return as_signed(bytes[1] << 8 | bytes[0], 16); }
18254
18255  function packU16(n) { return [n & 0xff, (n >> 8) & 0xff]; }
18256  function unpackU16(bytes) { return as_unsigned(bytes[1] << 8 | bytes[0], 16); }
18257
18258  function packI32(n) { return [n & 0xff, (n >> 8) & 0xff, (n >> 16) & 0xff, (n >> 24) & 0xff]; }
18259  function unpackI32(bytes) { return as_signed(bytes[3] << 24 | bytes[2] << 16 | bytes[1] << 8 | bytes[0], 32); }
18260
18261  function packU32(n) { return [n & 0xff, (n >> 8) & 0xff, (n >> 16) & 0xff, (n >> 24) & 0xff]; }
18262  function unpackU32(bytes) { return as_unsigned(bytes[3] << 24 | bytes[2] << 16 | bytes[1] << 8 | bytes[0], 32); }
18263
18264  function packIEEE754(v, ebits, fbits) {
18265
18266    var bias = (1 << (ebits - 1)) - 1;
18267
18268    function roundToEven(n) {
18269      var w = floor(n), f = n - w;
18270      if (f < 0.5)
18271        return w;
18272      if (f > 0.5)
18273        return w + 1;
18274      return w % 2 ? w + 1 : w;
18275    }
18276
18277    // Compute sign, exponent, fraction
18278    var s, e, f;
18279    if (v !== v) {
18280      // NaN
18281      // http://dev.w3.org/2006/webapi/WebIDL/#es-type-mapping
18282      e = (1 << ebits) - 1; f = pow(2, fbits - 1); s = 0;
18283    } else if (v === Infinity || v === -Infinity) {
18284      e = (1 << ebits) - 1; f = 0; s = (v < 0) ? 1 : 0;
18285    } else if (v === 0) {
18286      e = 0; f = 0; s = (1 / v === -Infinity) ? 1 : 0;
18287    } else {
18288      s = v < 0;
18289      v = abs(v);
18290
18291      if (v >= pow(2, 1 - bias)) {
18292        // Normalized
18293        e = min(floor(log(v) / LN2), 1023);
18294        var significand = v / pow(2, e);
18295        if (significand < 1) {
18296          e -= 1;
18297          significand *= 2;
18298        }
18299        if (significand >= 2) {
18300          e += 1;
18301          significand /= 2;
18302        }
18303        var d = pow(2, fbits);
18304        f = roundToEven(significand * d) - d;
18305        e += bias;
18306        if (f / d >= 1) {
18307          e += 1;
18308          f = 0;
18309        }
18310        if (e > 2 * bias) {
18311          // Overflow
18312          e = (1 << ebits) - 1;
18313          f = 0;
18314        }
18315      } else {
18316        // Denormalized
18317        e = 0;
18318        f = roundToEven(v / pow(2, 1 - bias - fbits));
18319      }
18320    }
18321
18322    // Pack sign, exponent, fraction
18323    var bits = [], i;
18324    for (i = fbits; i; i -= 1) { bits.push(f % 2 ? 1 : 0); f = floor(f / 2); }
18325    for (i = ebits; i; i -= 1) { bits.push(e % 2 ? 1 : 0); e = floor(e / 2); }
18326    bits.push(s ? 1 : 0);
18327    bits.reverse();
18328    var str = bits.join('');
18329
18330    // Bits to bytes
18331    var bytes = [];
18332    while (str.length) {
18333      bytes.unshift(parseInt(str.substring(0, 8), 2));
18334      str = str.substring(8);
18335    }
18336    return bytes;
18337  }
18338
18339  function unpackIEEE754(bytes, ebits, fbits) {
18340    // Bytes to bits
18341    var bits = [], i, j, b, str,
18342        bias, s, e, f;
18343
18344    for (i = 0; i < bytes.length; ++i) {
18345      b = bytes[i];
18346      for (j = 8; j; j -= 1) {
18347        bits.push(b % 2 ? 1 : 0); b = b >> 1;
18348      }
18349    }
18350    bits.reverse();
18351    str = bits.join('');
18352
18353    // Unpack sign, exponent, fraction
18354    bias = (1 << (ebits - 1)) - 1;
18355    s = parseInt(str.substring(0, 1), 2) ? -1 : 1;
18356    e = parseInt(str.substring(1, 1 + ebits), 2);
18357    f = parseInt(str.substring(1 + ebits), 2);
18358
18359    // Produce number
18360    if (e === (1 << ebits) - 1) {
18361      return f !== 0 ? NaN : s * Infinity;
18362    } else if (e > 0) {
18363      // Normalized
18364      return s * pow(2, e - bias) * (1 + f / pow(2, fbits));
18365    } else if (f !== 0) {
18366      // Denormalized
18367      return s * pow(2, -(bias - 1)) * (f / pow(2, fbits));
18368    } else {
18369      return s < 0 ? -0 : 0;
18370    }
18371  }
18372
18373  function unpackF64(b) { return unpackIEEE754(b, 11, 52); }
18374  function packF64(v) { return packIEEE754(v, 11, 52); }
18375  function unpackF32(b) { return unpackIEEE754(b, 8, 23); }
18376  function packF32(v) { return packIEEE754(v, 8, 23); }
18377
18378  //
18379  // 3 The ArrayBuffer Type
18380  //
18381
18382  (function() {
18383
18384    function ArrayBuffer(length) {
18385      length = ToInt32(length);
18386      if (length < 0) throw RangeError('ArrayBuffer size is not a small enough positive integer.');
18387      Object.defineProperty(this, 'byteLength', {value: length});
18388      Object.defineProperty(this, '_bytes', {value: Array(length)});
18389
18390      for (var i = 0; i < length; i += 1)
18391        this._bytes[i] = 0;
18392    }
18393
18394    global.ArrayBuffer = global.ArrayBuffer || ArrayBuffer;
18395
18396    //
18397    // 5 The Typed Array View Types
18398    //
18399
18400    function $TypedArray$() {
18401
18402      // %TypedArray% ( length )
18403      if (!arguments.length || typeof arguments[0] !== 'object') {
18404        return (function(length) {
18405          length = ToInt32(length);
18406          if (length < 0) throw RangeError('length is not a small enough positive integer.');
18407          Object.defineProperty(this, 'length', {value: length});
18408          Object.defineProperty(this, 'byteLength', {value: length * this.BYTES_PER_ELEMENT});
18409          Object.defineProperty(this, 'buffer', {value: new ArrayBuffer(this.byteLength)});
18410          Object.defineProperty(this, 'byteOffset', {value: 0});
18411
18412         }).apply(this, arguments);
18413      }
18414
18415      // %TypedArray% ( typedArray )
18416      if (arguments.length >= 1 &&
18417          Type(arguments[0]) === 'object' &&
18418          arguments[0] instanceof $TypedArray$) {
18419        return (function(typedArray){
18420          if (this.constructor !== typedArray.constructor) throw TypeError();
18421
18422          var byteLength = typedArray.length * this.BYTES_PER_ELEMENT;
18423          Object.defineProperty(this, 'buffer', {value: new ArrayBuffer(byteLength)});
18424          Object.defineProperty(this, 'byteLength', {value: byteLength});
18425          Object.defineProperty(this, 'byteOffset', {value: 0});
18426          Object.defineProperty(this, 'length', {value: typedArray.length});
18427
18428          for (var i = 0; i < this.length; i += 1)
18429            this._setter(i, typedArray._getter(i));
18430
18431        }).apply(this, arguments);
18432      }
18433
18434      // %TypedArray% ( array )
18435      if (arguments.length >= 1 &&
18436          Type(arguments[0]) === 'object' &&
18437          !(arguments[0] instanceof $TypedArray$) &&
18438          !(arguments[0] instanceof ArrayBuffer || Class(arguments[0]) === 'ArrayBuffer')) {
18439        return (function(array) {
18440
18441          var byteLength = array.length * this.BYTES_PER_ELEMENT;
18442          Object.defineProperty(this, 'buffer', {value: new ArrayBuffer(byteLength)});
18443          Object.defineProperty(this, 'byteLength', {value: byteLength});
18444          Object.defineProperty(this, 'byteOffset', {value: 0});
18445          Object.defineProperty(this, 'length', {value: array.length});
18446
18447          for (var i = 0; i < this.length; i += 1) {
18448            var s = array[i];
18449            this._setter(i, Number(s));
18450          }
18451        }).apply(this, arguments);
18452      }
18453
18454      // %TypedArray% ( buffer, byteOffset=0, length=undefined )
18455      if (arguments.length >= 1 &&
18456          Type(arguments[0]) === 'object' &&
18457          (arguments[0] instanceof ArrayBuffer || Class(arguments[0]) === 'ArrayBuffer')) {
18458        return (function(buffer, byteOffset, length) {
18459
18460          byteOffset = ToUint32(byteOffset);
18461          if (byteOffset > buffer.byteLength)
18462            throw RangeError('byteOffset out of range');
18463
18464          // The given byteOffset must be a multiple of the element
18465          // size of the specific type, otherwise an exception is raised.
18466          if (byteOffset % this.BYTES_PER_ELEMENT)
18467            throw RangeError('buffer length minus the byteOffset is not a multiple of the element size.');
18468
18469          if (length === undefined) {
18470            var byteLength = buffer.byteLength - byteOffset;
18471            if (byteLength % this.BYTES_PER_ELEMENT)
18472              throw RangeError('length of buffer minus byteOffset not a multiple of the element size');
18473            length = byteLength / this.BYTES_PER_ELEMENT;
18474
18475          } else {
18476            length = ToUint32(length);
18477            byteLength = length * this.BYTES_PER_ELEMENT;
18478          }
18479
18480          if ((byteOffset + byteLength) > buffer.byteLength)
18481            throw RangeError('byteOffset and length reference an area beyond the end of the buffer');
18482
18483          Object.defineProperty(this, 'buffer', {value: buffer});
18484          Object.defineProperty(this, 'byteLength', {value: byteLength});
18485          Object.defineProperty(this, 'byteOffset', {value: byteOffset});
18486          Object.defineProperty(this, 'length', {value: length});
18487
18488        }).apply(this, arguments);
18489      }
18490
18491      // %TypedArray% ( all other argument combinations )
18492      throw TypeError();
18493    }
18494
18495    // Properties of the %TypedArray Instrinsic Object
18496
18497    // %TypedArray%.from ( source , mapfn=undefined, thisArg=undefined )
18498    Object.defineProperty($TypedArray$, 'from', {value: function(iterable) {
18499      return new this(iterable);
18500    }});
18501
18502    // %TypedArray%.of ( ...items )
18503    Object.defineProperty($TypedArray$, 'of', {value: function(/*...items*/) {
18504      return new this(arguments);
18505    }});
18506
18507    // %TypedArray%.prototype
18508    var $TypedArrayPrototype$ = {};
18509    $TypedArray$.prototype = $TypedArrayPrototype$;
18510
18511    // WebIDL: getter type (unsigned long index);
18512    Object.defineProperty($TypedArray$.prototype, '_getter', {value: function(index) {
18513      if (arguments.length < 1) throw SyntaxError('Not enough arguments');
18514
18515      index = ToUint32(index);
18516      if (index >= this.length)
18517        return undefined;
18518
18519      var bytes = [], i, o;
18520      for (i = 0, o = this.byteOffset + index * this.BYTES_PER_ELEMENT;
18521           i < this.BYTES_PER_ELEMENT;
18522           i += 1, o += 1) {
18523        bytes.push(this.buffer._bytes[o]);
18524      }
18525      return this._unpack(bytes);
18526    }});
18527
18528    // NONSTANDARD: convenience alias for getter: type get(unsigned long index);
18529    Object.defineProperty($TypedArray$.prototype, 'get', {value: $TypedArray$.prototype._getter});
18530
18531    // WebIDL: setter void (unsigned long index, type value);
18532    Object.defineProperty($TypedArray$.prototype, '_setter', {value: function(index, value) {
18533      if (arguments.length < 2) throw SyntaxError('Not enough arguments');
18534
18535      index = ToUint32(index);
18536      if (index >= this.length)
18537        return;
18538
18539      var bytes = this._pack(value), i, o;
18540      for (i = 0, o = this.byteOffset + index * this.BYTES_PER_ELEMENT;
18541           i < this.BYTES_PER_ELEMENT;
18542           i += 1, o += 1) {
18543        this.buffer._bytes[o] = bytes[i];
18544      }
18545    }});
18546
18547    // get %TypedArray%.prototype.buffer
18548    // get %TypedArray%.prototype.byteLength
18549    // get %TypedArray%.prototype.byteOffset
18550    // -- applied directly to the object in the constructor
18551
18552    // %TypedArray%.prototype.constructor
18553    Object.defineProperty($TypedArray$.prototype, 'constructor', {value: $TypedArray$});
18554
18555    // %TypedArray%.prototype.copyWithin (target, start, end = this.length )
18556    Object.defineProperty($TypedArray$.prototype, 'copyWithin', {value: function(target, start) {
18557      var end = arguments[2];
18558
18559      var o = ToObject(this);
18560      var lenVal = o.length;
18561      var len = ToUint32(lenVal);
18562      len = max(len, 0);
18563      var relativeTarget = ToInt32(target);
18564      var to;
18565      if (relativeTarget < 0)
18566        to = max(len + relativeTarget, 0);
18567      else
18568        to = min(relativeTarget, len);
18569      var relativeStart = ToInt32(start);
18570      var from;
18571      if (relativeStart < 0)
18572        from = max(len + relativeStart, 0);
18573      else
18574        from = min(relativeStart, len);
18575      var relativeEnd;
18576      if (end === undefined)
18577        relativeEnd = len;
18578      else
18579        relativeEnd = ToInt32(end);
18580      var final;
18581      if (relativeEnd < 0)
18582        final = max(len + relativeEnd, 0);
18583      else
18584        final = min(relativeEnd, len);
18585      var count = min(final - from, len - to);
18586      var direction;
18587      if (from < to && to < from + count) {
18588        direction = -1;
18589        from = from + count - 1;
18590        to = to + count - 1;
18591      } else {
18592        direction = 1;
18593      }
18594      while (count > 0) {
18595        o._setter(to, o._getter(from));
18596        from = from + direction;
18597        to = to + direction;
18598        count = count - 1;
18599      }
18600      return o;
18601    }});
18602
18603    // %TypedArray%.prototype.entries ( )
18604    // -- defined in es6.js to shim browsers w/ native TypedArrays
18605
18606    // %TypedArray%.prototype.every ( callbackfn, thisArg = undefined )
18607    Object.defineProperty($TypedArray$.prototype, 'every', {value: function(callbackfn) {
18608      if (this === undefined || this === null) throw TypeError();
18609      var t = Object(this);
18610      var len = ToUint32(t.length);
18611      if (!IsCallable(callbackfn)) throw TypeError();
18612      var thisArg = arguments[1];
18613      for (var i = 0; i < len; i++) {
18614        if (!callbackfn.call(thisArg, t._getter(i), i, t))
18615          return false;
18616      }
18617      return true;
18618    }});
18619
18620    // %TypedArray%.prototype.fill (value, start = 0, end = this.length )
18621    Object.defineProperty($TypedArray$.prototype, 'fill', {value: function(value) {
18622      var start = arguments[1],
18623          end = arguments[2];
18624
18625      var o = ToObject(this);
18626      var lenVal = o.length;
18627      var len = ToUint32(lenVal);
18628      len = max(len, 0);
18629      var relativeStart = ToInt32(start);
18630      var k;
18631      if (relativeStart < 0)
18632        k = max((len + relativeStart), 0);
18633      else
18634        k = min(relativeStart, len);
18635      var relativeEnd;
18636      if (end === undefined)
18637        relativeEnd = len;
18638      else
18639        relativeEnd = ToInt32(end);
18640      var final;
18641      if (relativeEnd < 0)
18642        final = max((len + relativeEnd), 0);
18643      else
18644        final = min(relativeEnd, len);
18645      while (k < final) {
18646        o._setter(k, value);
18647        k += 1;
18648      }
18649      return o;
18650    }});
18651
18652    // %TypedArray%.prototype.filter ( callbackfn, thisArg = undefined )
18653    Object.defineProperty($TypedArray$.prototype, 'filter', {value: function(callbackfn) {
18654      if (this === undefined || this === null) throw TypeError();
18655      var t = Object(this);
18656      var len = ToUint32(t.length);
18657      if (!IsCallable(callbackfn)) throw TypeError();
18658      var res = [];
18659      var thisp = arguments[1];
18660      for (var i = 0; i < len; i++) {
18661        var val = t._getter(i); // in case fun mutates this
18662        if (callbackfn.call(thisp, val, i, t))
18663          res.push(val);
18664      }
18665      return new this.constructor(res);
18666    }});
18667
18668    // %TypedArray%.prototype.find (predicate, thisArg = undefined)
18669    Object.defineProperty($TypedArray$.prototype, 'find', {value: function(predicate) {
18670      var o = ToObject(this);
18671      var lenValue = o.length;
18672      var len = ToUint32(lenValue);
18673      if (!IsCallable(predicate)) throw TypeError();
18674      var t = arguments.length > 1 ? arguments[1] : undefined;
18675      var k = 0;
18676      while (k < len) {
18677        var kValue = o._getter(k);
18678        var testResult = predicate.call(t, kValue, k, o);
18679        if (Boolean(testResult))
18680          return kValue;
18681        ++k;
18682      }
18683      return undefined;
18684    }});
18685
18686    // %TypedArray%.prototype.findIndex ( predicate, thisArg = undefined )
18687    Object.defineProperty($TypedArray$.prototype, 'findIndex', {value: function(predicate) {
18688      var o = ToObject(this);
18689      var lenValue = o.length;
18690      var len = ToUint32(lenValue);
18691      if (!IsCallable(predicate)) throw TypeError();
18692      var t = arguments.length > 1 ? arguments[1] : undefined;
18693      var k = 0;
18694      while (k < len) {
18695        var kValue = o._getter(k);
18696        var testResult = predicate.call(t, kValue, k, o);
18697        if (Boolean(testResult))
18698          return k;
18699        ++k;
18700      }
18701      return -1;
18702    }});
18703
18704    // %TypedArray%.prototype.forEach ( callbackfn, thisArg = undefined )
18705    Object.defineProperty($TypedArray$.prototype, 'forEach', {value: function(callbackfn) {
18706      if (this === undefined || this === null) throw TypeError();
18707      var t = Object(this);
18708      var len = ToUint32(t.length);
18709      if (!IsCallable(callbackfn)) throw TypeError();
18710      var thisp = arguments[1];
18711      for (var i = 0; i < len; i++)
18712        callbackfn.call(thisp, t._getter(i), i, t);
18713    }});
18714
18715    // %TypedArray%.prototype.indexOf (searchElement, fromIndex = 0 )
18716    Object.defineProperty($TypedArray$.prototype, 'indexOf', {value: function(searchElement) {
18717      if (this === undefined || this === null) throw TypeError();
18718      var t = Object(this);
18719      var len = ToUint32(t.length);
18720      if (len === 0) return -1;
18721      var n = 0;
18722      if (arguments.length > 0) {
18723        n = Number(arguments[1]);
18724        if (n !== n) {
18725          n = 0;
18726        } else if (n !== 0 && n !== (1 / 0) && n !== -(1 / 0)) {
18727          n = (n > 0 || -1) * floor(abs(n));
18728        }
18729      }
18730      if (n >= len) return -1;
18731      var k = n >= 0 ? n : max(len - abs(n), 0);
18732      for (; k < len; k++) {
18733        if (t._getter(k) === searchElement) {
18734          return k;
18735        }
18736      }
18737      return -1;
18738    }});
18739
18740    // %TypedArray%.prototype.join ( separator )
18741    Object.defineProperty($TypedArray$.prototype, 'join', {value: function(separator) {
18742      if (this === undefined || this === null) throw TypeError();
18743      var t = Object(this);
18744      var len = ToUint32(t.length);
18745      var tmp = Array(len);
18746      for (var i = 0; i < len; ++i)
18747        tmp[i] = t._getter(i);
18748      return tmp.join(separator === undefined ? ',' : separator); // Hack for IE7
18749    }});
18750
18751    // %TypedArray%.prototype.keys ( )
18752    // -- defined in es6.js to shim browsers w/ native TypedArrays
18753
18754    // %TypedArray%.prototype.lastIndexOf ( searchElement, fromIndex = this.length-1 )
18755    Object.defineProperty($TypedArray$.prototype, 'lastIndexOf', {value: function(searchElement) {
18756      if (this === undefined || this === null) throw TypeError();
18757      var t = Object(this);
18758      var len = ToUint32(t.length);
18759      if (len === 0) return -1;
18760      var n = len;
18761      if (arguments.length > 1) {
18762        n = Number(arguments[1]);
18763        if (n !== n) {
18764          n = 0;
18765        } else if (n !== 0 && n !== (1 / 0) && n !== -(1 / 0)) {
18766          n = (n > 0 || -1) * floor(abs(n));
18767        }
18768      }
18769      var k = n >= 0 ? min(n, len - 1) : len - abs(n);
18770      for (; k >= 0; k--) {
18771        if (t._getter(k) === searchElement)
18772          return k;
18773      }
18774      return -1;
18775    }});
18776
18777    // get %TypedArray%.prototype.length
18778    // -- applied directly to the object in the constructor
18779
18780    // %TypedArray%.prototype.map ( callbackfn, thisArg = undefined )
18781    Object.defineProperty($TypedArray$.prototype, 'map', {value: function(callbackfn) {
18782      if (this === undefined || this === null) throw TypeError();
18783      var t = Object(this);
18784      var len = ToUint32(t.length);
18785      if (!IsCallable(callbackfn)) throw TypeError();
18786      var res = []; res.length = len;
18787      var thisp = arguments[1];
18788      for (var i = 0; i < len; i++)
18789        res[i] = callbackfn.call(thisp, t._getter(i), i, t);
18790      return new this.constructor(res);
18791    }});
18792
18793    // %TypedArray%.prototype.reduce ( callbackfn [, initialValue] )
18794    Object.defineProperty($TypedArray$.prototype, 'reduce', {value: function(callbackfn) {
18795      if (this === undefined || this === null) throw TypeError();
18796      var t = Object(this);
18797      var len = ToUint32(t.length);
18798      if (!IsCallable(callbackfn)) throw TypeError();
18799      // no value to return if no initial value and an empty array
18800      if (len === 0 && arguments.length === 1) throw TypeError();
18801      var k = 0;
18802      var accumulator;
18803      if (arguments.length >= 2) {
18804        accumulator = arguments[1];
18805      } else {
18806        accumulator = t._getter(k++);
18807      }
18808      while (k < len) {
18809        accumulator = callbackfn.call(undefined, accumulator, t._getter(k), k, t);
18810        k++;
18811      }
18812      return accumulator;
18813    }});
18814
18815    // %TypedArray%.prototype.reduceRight ( callbackfn [, initialValue] )
18816    Object.defineProperty($TypedArray$.prototype, 'reduceRight', {value: function(callbackfn) {
18817      if (this === undefined || this === null) throw TypeError();
18818      var t = Object(this);
18819      var len = ToUint32(t.length);
18820      if (!IsCallable(callbackfn)) throw TypeError();
18821      // no value to return if no initial value, empty array
18822      if (len === 0 && arguments.length === 1) throw TypeError();
18823      var k = len - 1;
18824      var accumulator;
18825      if (arguments.length >= 2) {
18826        accumulator = arguments[1];
18827      } else {
18828        accumulator = t._getter(k--);
18829      }
18830      while (k >= 0) {
18831        accumulator = callbackfn.call(undefined, accumulator, t._getter(k), k, t);
18832        k--;
18833      }
18834      return accumulator;
18835    }});
18836
18837    // %TypedArray%.prototype.reverse ( )
18838    Object.defineProperty($TypedArray$.prototype, 'reverse', {value: function() {
18839      if (this === undefined || this === null) throw TypeError();
18840      var t = Object(this);
18841      var len = ToUint32(t.length);
18842      var half = floor(len / 2);
18843      for (var i = 0, j = len - 1; i < half; ++i, --j) {
18844        var tmp = t._getter(i);
18845        t._setter(i, t._getter(j));
18846        t._setter(j, tmp);
18847      }
18848      return t;
18849    }});
18850
18851    // %TypedArray%.prototype.set(array, offset = 0 )
18852    // %TypedArray%.prototype.set(typedArray, offset = 0 )
18853    // WebIDL: void set(TypedArray array, optional unsigned long offset);
18854    // WebIDL: void set(sequence<type> array, optional unsigned long offset);
18855    Object.defineProperty($TypedArray$.prototype, 'set', {value: function(index, value) {
18856      if (arguments.length < 1) throw SyntaxError('Not enough arguments');
18857      var array, sequence, offset, len,
18858          i, s, d,
18859          byteOffset, byteLength, tmp;
18860
18861      if (typeof arguments[0] === 'object' && arguments[0].constructor === this.constructor) {
18862        // void set(TypedArray array, optional unsigned long offset);
18863        array = arguments[0];
18864        offset = ToUint32(arguments[1]);
18865
18866        if (offset + array.length > this.length) {
18867          throw RangeError('Offset plus length of array is out of range');
18868        }
18869
18870        byteOffset = this.byteOffset + offset * this.BYTES_PER_ELEMENT;
18871        byteLength = array.length * this.BYTES_PER_ELEMENT;
18872
18873        if (array.buffer === this.buffer) {
18874          tmp = [];
18875          for (i = 0, s = array.byteOffset; i < byteLength; i += 1, s += 1) {
18876            tmp[i] = array.buffer._bytes[s];
18877          }
18878          for (i = 0, d = byteOffset; i < byteLength; i += 1, d += 1) {
18879            this.buffer._bytes[d] = tmp[i];
18880          }
18881        } else {
18882          for (i = 0, s = array.byteOffset, d = byteOffset;
18883               i < byteLength; i += 1, s += 1, d += 1) {
18884            this.buffer._bytes[d] = array.buffer._bytes[s];
18885          }
18886        }
18887      } else if (typeof arguments[0] === 'object' && typeof arguments[0].length !== 'undefined') {
18888        // void set(sequence<type> array, optional unsigned long offset);
18889        sequence = arguments[0];
18890        len = ToUint32(sequence.length);
18891        offset = ToUint32(arguments[1]);
18892
18893        if (offset + len > this.length) {
18894          throw RangeError('Offset plus length of array is out of range');
18895        }
18896
18897        for (i = 0; i < len; i += 1) {
18898          s = sequence[i];
18899          this._setter(offset + i, Number(s));
18900        }
18901      } else {
18902        throw TypeError('Unexpected argument type(s)');
18903      }
18904    }});
18905
18906    // %TypedArray%.prototype.slice ( start, end )
18907    Object.defineProperty($TypedArray$.prototype, 'slice', {value: function(start, end) {
18908      var o = ToObject(this);
18909      var lenVal = o.length;
18910      var len = ToUint32(lenVal);
18911      var relativeStart = ToInt32(start);
18912      var k = (relativeStart < 0) ? max(len + relativeStart, 0) : min(relativeStart, len);
18913      var relativeEnd = (end === undefined) ? len : ToInt32(end);
18914      var final = (relativeEnd < 0) ? max(len + relativeEnd, 0) : min(relativeEnd, len);
18915      var count = final - k;
18916      var c = o.constructor;
18917      var a = new c(count);
18918      var n = 0;
18919      while (k < final) {
18920        var kValue = o._getter(k);
18921        a._setter(n, kValue);
18922        ++k;
18923        ++n;
18924      }
18925      return a;
18926    }});
18927
18928    // %TypedArray%.prototype.some ( callbackfn, thisArg = undefined )
18929    Object.defineProperty($TypedArray$.prototype, 'some', {value: function(callbackfn) {
18930      if (this === undefined || this === null) throw TypeError();
18931      var t = Object(this);
18932      var len = ToUint32(t.length);
18933      if (!IsCallable(callbackfn)) throw TypeError();
18934      var thisp = arguments[1];
18935      for (var i = 0; i < len; i++) {
18936        if (callbackfn.call(thisp, t._getter(i), i, t)) {
18937          return true;
18938        }
18939      }
18940      return false;
18941    }});
18942
18943    // %TypedArray%.prototype.sort ( comparefn )
18944    Object.defineProperty($TypedArray$.prototype, 'sort', {value: function(comparefn) {
18945      if (this === undefined || this === null) throw TypeError();
18946      var t = Object(this);
18947      var len = ToUint32(t.length);
18948      var tmp = Array(len);
18949      for (var i = 0; i < len; ++i)
18950        tmp[i] = t._getter(i);
18951      
18951if (comparefn) tmp.sort(comparefn); else tmp.sort(); // Hack for IE8/9
vendor: 8,088 bytes, lines 18951-19151
18951
18952      for (i = 0; i < len; ++i)
18953        t._setter(i, tmp[i]);
18954      return t;
18955    }});
18956
18957    // %TypedArray%.prototype.subarray(begin = 0, end = this.length )
18958    // WebIDL: TypedArray subarray(long begin, optional long end);
18959    Object.defineProperty($TypedArray$.prototype, 'subarray', {value: function(start, end) {
18960      function clamp(v, min, max) { return v < min ? min : v > max ? max : v; }
18961
18962      start = ToInt32(start);
18963      end = ToInt32(end);
18964
18965      if (arguments.length < 1) { start = 0; }
18966      if (arguments.length < 2) { end = this.length; }
18967
18968      if (start < 0) { start = this.length + start; }
18969      if (end < 0) { end = this.length + end; }
18970
18971      start = clamp(start, 0, this.length);
18972      end = clamp(end, 0, this.length);
18973
18974      var len = end - start;
18975      if (len < 0) {
18976        len = 0;
18977      }
18978
18979      return new this.constructor(
18980        this.buffer, this.byteOffset + start * this.BYTES_PER_ELEMENT, len);
18981    }});
18982
18983    // %TypedArray%.prototype.toLocaleString ( )
18984    // %TypedArray%.prototype.toString ( )
18985    // %TypedArray%.prototype.values ( )
18986    // %TypedArray%.prototype [ @@iterator ] ( )
18987    // get %TypedArray%.prototype [ @@toStringTag ]
18988    // -- defined in es6.js to shim browsers w/ native TypedArrays
18989
18990    function makeTypedArray(elementSize, pack, unpack) {
18991      // Each TypedArray type requires a distinct constructor instance with
18992      // identical logic, which this produces.
18993      var TypedArray = function() {
18994        Object.defineProperty(this, 'constructor', {value: TypedArray});
18995        $TypedArray$.apply(this, arguments);
18996        makeArrayAccessors(this);
18997      };
18998      if ('__proto__' in TypedArray) {
18999        TypedArray.__proto__ = $TypedArray$;
19000      } else {
19001        TypedArray.from = $TypedArray$.from;
19002        TypedArray.of = $TypedArray$.of;
19003      }
19004
19005      TypedArray.BYTES_PER_ELEMENT = elementSize;
19006
19007      var TypedArrayPrototype = function() {};
19008      TypedArrayPrototype.prototype = $TypedArrayPrototype$;
19009
19010      TypedArray.prototype = new TypedArrayPrototype();
19011
19012      Object.defineProperty(TypedArray.prototype, 'BYTES_PER_ELEMENT', {value: elementSize});
19013      Object.defineProperty(TypedArray.prototype, '_pack', {value: pack});
19014      Object.defineProperty(TypedArray.prototype, '_unpack', {value: unpack});
19015
19016      return TypedArray;
19017    }
19018
19019    var Int8Array = makeTypedArray(1, packI8, unpackI8);
19020    var Uint8Array = makeTypedArray(1, packU8, unpackU8);
19021    var Uint8ClampedArray = makeTypedArray(1, packU8Clamped, unpackU8);
19022    var Int16Array = makeTypedArray(2, packI16, unpackI16);
19023    var Uint16Array = makeTypedArray(2, packU16, unpackU16);
19024    var Int32Array = makeTypedArray(4, packI32, unpackI32);
19025    var Uint32Array = makeTypedArray(4, packU32, unpackU32);
19026    var Float32Array = makeTypedArray(4, packF32, unpackF32);
19027    var Float64Array = makeTypedArray(8, packF64, unpackF64);
19028
19029    global.Int8Array = global.Int8Array || Int8Array;
19030    global.Uint8Array = global.Uint8Array || Uint8Array;
19031    global.Uint8ClampedArray = global.Uint8ClampedArray || Uint8ClampedArray;
19032    global.Int16Array = global.Int16Array || Int16Array;
19033    global.Uint16Array = global.Uint16Array || Uint16Array;
19034    global.Int32Array = global.Int32Array || Int32Array;
19035    global.Uint32Array = global.Uint32Array || Uint32Array;
19036    global.Float32Array = global.Float32Array || Float32Array;
19037    global.Float64Array = global.Float64Array || Float64Array;
19038  }());
19039
19040  //
19041  // 6 The DataView View Type
19042  //
19043
19044  (function() {
19045    function r(array, index) {
19046      return IsCallable(array.get) ? array.get(index) : array[index];
19047    }
19048
19049    var IS_BIG_ENDIAN = (function() {
19050      var u16array = new Uint16Array([0x1234]),
19051          u8array = new Uint8Array(u16array.buffer);
19052      return r(u8array, 0) === 0x12;
19053    }());
19054
19055    // DataView(buffer, byteOffset=0, byteLength=undefined)
19056    // WebIDL: Constructor(ArrayBuffer buffer,
19057    //                     optional unsigned long byteOffset,
19058    //                     optional unsigned long byteLength)
19059    function DataView(buffer, byteOffset, byteLength) {
19060      if (!(buffer instanceof ArrayBuffer || Class(buffer) === 'ArrayBuffer')) throw TypeError();
19061
19062      byteOffset = ToUint32(byteOffset);
19063      if (byteOffset > buffer.byteLength)
19064        throw RangeError('byteOffset out of range');
19065
19066      if (byteLength === undefined)
19067        byteLength = buffer.byteLength - byteOffset;
19068      else
19069        byteLength = ToUint32(byteLength);
19070
19071      if ((byteOffset + byteLength) > buffer.byteLength)
19072        throw RangeError('byteOffset and length reference an area beyond the end of the buffer');
19073
19074      Object.defineProperty(this, 'buffer', {value: buffer});
19075      Object.defineProperty(this, 'byteLength', {value: byteLength});
19076      Object.defineProperty(this, 'byteOffset', {value: byteOffset});
19077    };
19078
19079    // get DataView.prototype.buffer
19080    // get DataView.prototype.byteLength
19081    // get DataView.prototype.byteOffset
19082    // -- applied directly to instances by the constructor
19083
19084    function makeGetter(arrayType) {
19085      return function GetViewValue(byteOffset, littleEndian) {
19086        byteOffset = ToUint32(byteOffset);
19087
19088        if (byteOffset + arrayType.BYTES_PER_ELEMENT > this.byteLength)
19089          throw RangeError('Array index out of range');
19090
19091        byteOffset += this.byteOffset;
19092
19093        var uint8Array = new Uint8Array(this.buffer, byteOffset, arrayType.BYTES_PER_ELEMENT),
19094            bytes = [];
19095        for (var i = 0; i < arrayType.BYTES_PER_ELEMENT; i += 1)
19096          bytes.push(r(uint8Array, i));
19097
19098        if (Boolean(littleEndian) === Boolean(IS_BIG_ENDIAN))
19099          bytes.reverse();
19100
19101        return r(new arrayType(new Uint8Array(bytes).buffer), 0);
19102      };
19103    }
19104
19105    Object.defineProperty(DataView.prototype, 'getUint8', {value: makeGetter(Uint8Array)});
19106    Object.defineProperty(DataView.prototype, 'getInt8', {value: makeGetter(Int8Array)});
19107    Object.defineProperty(DataView.prototype, 'getUint16', {value: makeGetter(Uint16Array)});
19108    Object.defineProperty(DataView.prototype, 'getInt16', {value: makeGetter(Int16Array)});
19109    Object.defineProperty(DataView.prototype, 'getUint32', {value: makeGetter(Uint32Array)});
19110    Object.defineProperty(DataView.prototype, 'getInt32', {value: makeGetter(Int32Array)});
19111    Object.defineProperty(DataView.prototype, 'getFloat32', {value: makeGetter(Float32Array)});
19112    Object.defineProperty(DataView.prototype, 'getFloat64', {value: makeGetter(Float64Array)});
19113
19114    function makeSetter(arrayType) {
19115      return function SetViewValue(byteOffset, value, littleEndian) {
19116        byteOffset = ToUint32(byteOffset);
19117        if (byteOffset + arrayType.BYTES_PER_ELEMENT > this.byteLength)
19118          throw RangeError('Array index out of range');
19119
19120        // Get bytes
19121        var typeArray = new arrayType([value]),
19122            byteArray = new Uint8Array(typeArray.buffer),
19123            bytes = [], i, byteView;
19124
19125        for (i = 0; i < arrayType.BYTES_PER_ELEMENT; i += 1)
19126          bytes.push(r(byteArray, i));
19127
19128        // Flip if necessary
19129        if (Boolean(littleEndian) === Boolean(IS_BIG_ENDIAN))
19130          bytes.reverse();
19131
19132        // Write them
19133        byteView = new Uint8Array(this.buffer, byteOffset, arrayType.BYTES_PER_ELEMENT);
19134        byteView.set(bytes);
19135      };
19136    }
19137
19138    Object.defineProperty(DataView.prototype, 'setUint8', {value: makeSetter(Uint8Array)});
19139    Object.defineProperty(DataView.prototype, 'setInt8', {value: makeSetter(Int8Array)});
19140    Object.defineProperty(DataView.prototype, 'setUint16', {value: makeSetter(Uint16Array)});
19141    Object.defineProperty(DataView.prototype, 'setInt16', {value: makeSetter(Int16Array)});
19142    Object.defineProperty(DataView.prototype, 'setUint32', {value: makeSetter(Uint32Array)});
19143    Object.defineProperty(DataView.prototype, 'setInt32', {value: makeSetter(Int32Array)});
19144    Object.defineProperty(DataView.prototype, 'setFloat32', {value: makeSetter(Float32Array)});
19145    Object.defineProperty(DataView.prototype, 'setFloat64', {value: makeSetter(Float64Array)});
19146
19147    global.DataView = global.DataView || DataView;
19148
19149  }());
19150
19151}(self));
19152
19153// https://github.com/ttaubert/node-arraybuffer-slice
19154// (c) 2014 Tim Taubert <[email protected]>
19155// arraybuffer-slice may be freely distributed under the MIT license.
19156
19157(function (undefined) {
19158  "use strict";
19159
19160  function clamp(val, length) {
19161    val = (val|0) || 0;
19162
19163    if (val < 0) {
19164      return Math.max(val + length, 0);
19165    }
19166
19167    return Math.min(val, length);
19168  }
19169
19170  if (!ArrayBuffer.prototype.slice) {
19171    ArrayBuffer.prototype.slice = function (from, to) {
19172      var length = this.byteLength;
19173      var begin = clamp(from, length);
19174      var end = length;
19175
19176      if (to !== undefined) {
19177        end = clamp(to, length);
19178      }
19179
19180      if (begin > end) {
19181        return new ArrayBuffer(0);
19182      }
19183
19184      var num = end - begin;
19185      var target = new ArrayBuffer(num);
19186      var targetArray = new Uint8Array(target);
19187
19188      var sourceArray = new Uint8Array(this, begin, num);
19189      targetArray.set(sourceArray);
19190
19191      return target;
19192    };
19193  }
19194})();
19195
19196// This is free and unencumbered software released into the public domain.
19197// See LICENSE.md for more information.
19198
19199// If we're in node require encoding-indexes and attach it to the global.
19200/**
19201 * @fileoverview Global |this| required for resolving indexes in node.
19202 * @suppress {globalThis}
19203 */
19204if (typeof module !== "undefined" && module.exports) {
19205  this["encoding-indexes"] =
19206    require("./encoding-indexes.js")["encoding-indexes"];
19207}
19208
19209(function(global) {
19210  'use strict';
19211
19212  //
19213  // Utilities
19214  //
19215
19216  /**
19217   * @param {number} a The number to test.
19218   * @param {number} min The minimum value in the range, inclusive.
19219   * @param {number} max The maximum value in the range, inclusive.
19220   * @return {boolean} True if a >= min and a <= max.
19221   */
19222  function inRange(a, min, max) {
19223    return min <= a && a <= max;
19224  }
19225
19226  /**
19227   * @param {!Array.<*>} array The array to check.
19228   * @param {*} item The item to look for in the array.
19229   * @return {boolean} True if the item appears in the array.
19230   */
19231  function includes(array, item) {
19232    return array.indexOf(item) !== -1;
19233  }
19234
19235  var floor = Math.floor;
19236
19237  /**
19238   * @param {*} o
19239   * @return {Object}
19240   */
19241  function ToDictionary(o) {
19242    if (o === undefined) return {};
19243    if (o === Object(o)) return o;
19244    throw TypeError('Could not convert argument to dictionary');
19245  }
19246
19247  /**
19248   * @param {string} string Input string of UTF-16 code units.
19249   * @return {!Array.<number>} Code points.
19250   */
19251  function stringToCodePoints(string) {
19252    // https://heycam.github.io/webidl/#dfn-obtain-unicode
19253
19254    // 1. Let S be the DOMString value.
19255    var s = String(string);
19256
19257    // 2. Let n be the length of S.
19258    var n = s.length;
19259
19260    // 3. Initialize i to 0.
19261    var i = 0;
19262
19263    // 4. Initialize U to be an empty sequence of Unicode characters.
19264    var u = [];
19265
19266    // 5. While i < n:
19267    while (i < n) {
19268
19269      // 1. Let c be the code unit in S at index i.
19270      var c = s.charCodeAt(i);
19271
19272      // 2. Depending on the value of c:
19273
19274      // c < 0xD800 or c > 0xDFFF
19275      if (c < 0xD800 || c > 0xDFFF) {
19276        // Append to U the Unicode character with code point c.
19277        u.push(c);
19278      }
19279
19280      // 0xDC00 ≤ c ≤ 0xDFFF
19281      else if (0xDC00 <= c && c <= 0xDFFF) {
19282        // Append to U a U+FFFD REPLACEMENT CHARACTER.
19283        u.push(0xFFFD);
19284      }
19285
19286      // 0xD800 ≤ c ≤ 0xDBFF
19287      else if (0xD800 <= c && c <= 0xDBFF) {
19288        // 1. If i = n−1, then append to U a U+FFFD REPLACEMENT
19289        // CHARACTER.
19290        if (i === n - 1) {
19291          u.push(0xFFFD);
19292        }
19293        // 2. Otherwise, i < n−1:
19294        else {
19295          // 1. Let d be the code unit in S at index i+1.
19296          var d = string.charCodeAt(i + 1);
19297
19298          // 2. If 0xDC00 ≤ d ≤ 0xDFFF, then:
19299          if (0xDC00 <= d && d <= 0xDFFF) {
19300            // 1. Let a be c & 0x3FF.
19301            var a = c & 0x3FF;
19302
19303            // 2. Let b be d & 0x3FF.
19304            var b = d & 0x3FF;
19305
19306            // 3. Append to U the Unicode character with code point
19307            // 2^16+2^10*a+b.
19308            u.push(0x10000 + (a << 10) + b);
19309
19310            // 4. Set i to i+1.
19311            i += 1;
19312          }
19313
19314          // 3. Otherwise, d < 0xDC00 or d > 0xDFFF. Append to U a
19315          // U+FFFD REPLACEMENT CHARACTER.
19316          else  {
19317            u.push(0xFFFD);
19318          }
19319        }
19320      }
19321
19322      // 3. Set i to i+1.
19323      i += 1;
19324    }
19325
19326    // 6. Return U.
19327    return u;
19328  }
19329
19330  /**
19331   * @param {!Array.<number>} code_points Array of code points.
19332   * @return {string} string String of UTF-16 code units.
19333   */
19334  function codePointsToString(code_points) {
19335    var s = '';
19336    for (var i = 0; i < code_points.length; ++i) {
19337      var cp = code_points[i];
19338      if (cp <= 0xFFFF) {
19339        s += String.fromCharCode(cp);
19340      } else {
19341        cp -= 0x10000;
19342        s += String.fromCharCode((cp >> 10) + 0xD800,
19343                                 (cp & 0x3FF) + 0xDC00);
19344      }
19345    }
19346    return s;
19347  }
19348
19349
19350  //
19351  // Implementation of Encoding specification
19352  // https://encoding.spec.whatwg.org/
19353  //
19354
19355  //
19356  // 4. Terminology
19357  //
19358
19359  /**
19360   * An ASCII byte is a byte in the range 0x00 to 0x7F, inclusive.
19361   * @param {number} a The number to test.
19362   * @return {boolean} True if a is in the range 0x00 to 0x7F, inclusive.
19363   */
19364  function isASCIIByte(a) {
19365    return 0x00 <= a && a <= 0x7F;
19366  }
19367
19368  /**
19369   * An ASCII code point is a code point in the range U+0000 to
19370   * U+007F, inclusive.
19371   */
19372  var isASCIICodePoint = isASCIIByte;
19373
19374
19375  /**
19376   * End-of-stream is a special token that signifies no more tokens
19377   * are in the stream.
19378   * @const
19379   */ var end_of_stream = -1;
19380
19381  /**
19382   * A stream represents an ordered sequence of tokens.
19383   *
19384   * @constructor
19385   * @param {!(Array.<number>|Uint8Array)} tokens Array of tokens that provide
19386   * the stream.
19387   */
19388  function Stream(tokens) {
19389    /** @type {!Array.<number>} */
19390    this.tokens = [].slice.call(tokens);
19391    // Reversed as push/pop is more efficient than shift/unshift.
19392    this.tokens.reverse();
19393  }
19394
19395  Stream.prototype = {
19396    /**
19397     * @return {boolean} True if end-of-stream has been hit.
19398     */
19399    endOfStream: function() {
19400      return !this.tokens.length;
19401    },
19402
19403    /**
19404     * When a token is read from a stream, the first token in the
19405     * stream must be returned and subsequently removed, and
19406     * end-of-stream must be returned otherwise.
19407     *
19408     * @return {number} Get the next token from the stream, or
19409     * end_of_stream.
19410     */
19411     read: function() {
19412      if (!this.tokens.length)
19413        return end_of_stream;
19414       return this.tokens.pop();
19415     },
19416
19417    /**
19418     * When one or more tokens are prepended to a stream, those tokens
19419     * must be inserted, in given order, before the first token in the
19420     * stream.
19421     *
19422     * @param {(number|!Array.<number>)} token The token(s) to prepend to the
19423     * stream.
19424     */
19425    prepend: function(token) {
19426      if (Array.isArray(token)) {
19427        var tokens = /**@type {!Array.<number>}*/(token);
19428        while (tokens.length)
19429          this.tokens.push(tokens.pop());
19430      } else {
19431        this.tokens.push(token);
19432      }
19433    },
19434
19435    /**
19436     * When one or more tokens are pushed to a stream, those tokens
19437     * must be inserted, in given order, after the last token in the
19438     * stream.
19439     *
19440     * @param {(number|!Array.<number>)} token The tokens(s) to push to the
19441     * stream.
19442     */
19443    push: function(token) {
19444      if (Array.isArray(token)) {
19445        var tokens = /**@type {!Array.<number>}*/(token);
19446        while (tokens.length)
19447          this.tokens.unshift(tokens.shift());
19448      } else {
19449        this.tokens.unshift(token);
19450      }
19451    }
19452  };
19453
19454  //
19455  // 5. Encodings
19456  //
19457
19458  // 5.1 Encoders and decoders
19459
19460  /** @const */
19461  var finished = -1;
19462
19463  /**
19464   * @param {boolean} fatal If true, decoding errors raise an exception.
19465   * @param {number=} opt_code_point Override the standard fallback code point.
19466   * @return {number} The code point to insert on a decoding error.
19467   */
19468  function decoderError(fatal, opt_code_point) {
19469    if (fatal)
19470      throw TypeError('Decoder error');
19471    return opt_code_point || 0xFFFD;
19472  }
19473
19474  /**
19475   * @param {number} code_point The code point that could not be encoded.
19476   * @return {number} Always throws, no value is actually returned.
19477   */
19478  function encoderError(code_point) {
19479    throw TypeError('The code point ' + code_point + ' could not be encoded.');
19480  }
19481
19482  /** @interface */
19483  function Decoder() {}
19484  Decoder.prototype = {
19485    /**
19486     * @param {Stream} stream The stream of bytes being decoded.
19487     * @param {number} bite The next byte read from the stream.
19488     * @return {?(number|!Array.<number>)} The next code point(s)
19489     *     decoded, or null if not enough data exists in the input
19490     *     stream to decode a complete code point, or |finished|.
19491     */
19492    handler: function(stream, bite) {}
19493  };
19494
19495  /** @interface */
19496  function Encoder() {}
19497  Encoder.prototype = {
19498    /**
19499     * @param {Stream} stream The stream of code points being encoded.
19500     * @param {number} code_point Next code point read from the stream.
19501     * @return {(number|!Array.<number>)} Byte(s) to emit, or |finished|.
19502     */
19503    handler: function(stream, code_point) {}
19504  };
19505
19506  // 5.2 Names and labels
19507
19508  // TODO: Define @typedef for Encoding: {name:string,labels:Array.<string>}
19509  // https://github.com/google/closure-compiler/issues/247
19510
19511  /**
19512   * @param {string} label The encoding label.
19513   * @return {?{name:string,labels:Array.<string>}}
19514   */
19515  function getEncoding(label) {
19516    // 1. Remove any leading and trailing ASCII whitespace from label.
19517    label = String(label).trim().toLowerCase();
19518
19519    // 2. If label is an ASCII case-insensitive match for any of the
19520    // labels listed in the table below, return the corresponding
19521    // encoding, and failure otherwise.
19522    if (Object.prototype.hasOwnProperty.call(label_to_encoding, label)) {
19523      return label_to_encoding[label];
19524    }
19525    return null;
19526  }
19527
19528  /**
19529   * Encodings table: https://encoding.spec.whatwg.org/encodings.json
19530   * @const
19531   * @type {!Array.<{
19532   *          heading: string,
19533   *          encodings: Array.<{name:string,labels:Array.<string>}>
19534   *        }>}
19535   */
19536  var encodings = [
19537    {
19538      "encodings": [
19539        {
19540          "labels": [
19541            "unicode-1-1-utf-8",
19542            "utf-8",
19543            "utf8"
19544          ],
19545          "name": "UTF-8"
19546        }
19547      ],
19548      "heading": "The Encoding"
19549    },
19550    {
19551      "encodings": [
19552        {
19553          "labels": [
19554            "866",
19555            "cp866",
19556            "csibm866",
19557            "ibm866"
19558          ],
19559          "name": "IBM866"
19560        },
19561        {
19562          "labels": [
19563            "csisolatin2",
19564            "iso-8859-2",
19565            "iso-ir-101",
19566            "iso8859-2",
19567            "iso88592",
19568            "iso_8859-2",
19569            "iso_8859-2:1987",
19570            "l2",
19571            "latin2"
19572          ],
19573          "name": "ISO-8859-2"
19574        },
19575        {
19576          "labels": [
19577            "csisolatin3",
19578            "iso-8859-3",
19579            "iso-ir-109",
19580            "iso8859-3",
19581            "iso88593",
19582            "iso_8859-3",
19583            "iso_8859-3:1988",
19584            "l3",
19585            "latin3"
19586          ],
19587          "name": "ISO-8859-3"
19588        },
19589        {
19590          "labels": [
19591            "csisolatin4",
19592            "iso-8859-4",
19593            "iso-ir-110",
19594            "iso8859-4",
19595            "iso88594",
19596            "iso_8859-4",
19597            "iso_8859-4:1988",
19598            "l4",
19599            "latin4"
19600          ],
19601          "name": "ISO-8859-4"
19602        },
19603        {
19604          "labels": [
19605            "csisolatincyrillic",
19606            "cyrillic",
19607            "iso-8859-5",
19608            "iso-ir-144",
19609            "iso8859-5",
19610            "iso88595",
19611            "iso_8859-5",
19612            "iso_8859-5:1988"
19613          ],
19614          "name": "ISO-8859-5"
19615        },
19616        {
19617          "labels": [
19618            "arabic",
19619            "asmo-708",
19620            "csiso88596e",
19621            "csiso88596i",
19622            "csisolatinarabic",
19623            "ecma-114",
19624            "iso-8859-6",
19625            "iso-8859-6-e",
19626            "iso-8859-6-i",
19627            "iso-ir-127",
19628            "iso8859-6",
19629            "iso88596",
19630            "iso_8859-6",
19631            "iso_8859-6:1987"
19632          ],
19633          "name": "ISO-8859-6"
19634        },
19635        {
19636          "labels": [
19637            "csisolatingreek",
19638            "ecma-118",
19639            "elot_928",
19640            "greek",
19641            "greek8",
19642            "iso-8859-7",
19643            "iso-ir-126",
19644            "iso8859-7",
19645            "iso88597",
19646            "iso_8859-7",
19647            "iso_8859-7:1987",
19648            "sun_eu_greek"
19649          ],
19650          "name": "ISO-8859-7"
19651        },
19652        {
19653          "labels": [
19654            "csiso88598e",
19655            "csisolatinhebrew",
19656            "hebrew",
19657            "iso-8859-8",
19658            "iso-8859-8-e",
19659            "iso-ir-138",
19660            "iso8859-8",
19661            "iso88598",
19662            "iso_8859-8",
19663            "iso_8859-8:1988",
19664            "visual"
19665          ],
19666          "name": "ISO-8859-8"
19667        },
19668        {
19669          "labels": [
19670            "csiso88598i",
19671            "iso-8859-8-i",
19672            "logical"
19673          ],
19674          "name": "ISO-8859-8-I"
19675        },
19676        {
19677          "labels": [
19678            "csisolatin6",
19679            "iso-8859-10",
19680            "iso-ir-157",
19681            "iso8859-10",
19682            "iso885910",
19683            "l6",
19684            "latin6"
19685          ],
19686          "name": "ISO-8859-10"
19687        },
19688        {
19689          "labels": [
19690            "iso-8859-13",
19691            "iso8859-13",
19692            "iso885913"
19693          ],
19694          "name": "ISO-8859-13"
19695        },
19696        {
19697          "labels": [
19698            "iso-8859-14",
19699            "iso8859-14",
19700            "iso885914"
19701          ],
19702          "name": "ISO-8859-14"
19703        },
19704        {
19705          "labels": [
19706            "csisolatin9",
19707            "iso-8859-15",
19708            "iso8859-15",
19709            "iso885915",
19710            "iso_8859-15",
19711            "l9"
19712          ],
19713          "name": "ISO-8859-15"
19714        },
19715        {
19716          "labels": [
19717            "iso-8859-16"
19718          ],
19719          "name": "ISO-8859-16"
19720        },
19721        {
19722          "labels": [
19723            "cskoi8r",
19724            "koi",
19725            "koi8",
19726            "koi8-r",
19727            "koi8_r"
19728          ],
19729          "name": "KOI8-R"
19730        },
19731        {
19732          "labels": [
19733            "koi8-ru",
19734            "koi8-u"
19735          ],
19736          "name": "KOI8-U"
19737        },
19738        {
19739          "labels": [
19740            "csmacintosh",
19741            "mac",
19742            "macintosh",
19743            "x-mac-roman"
19744          ],
19745          "name": "macintosh"
19746        },
19747        {
19748          "labels": [
19749            "dos-874",
19750            "iso-8859-11",
19751            "iso8859-11",
19752            "iso885911",
19753            "tis-620",
19754            "windows-874"
19755          ],
19756          "name": "windows-874"
19757        },
19758        {
19759          "labels": [
19760            "cp1250",
19761            "windows-1250",
19762            "x-cp1250"
19763          ],
19764          "name": "windows-1250"
19765        },
19766        {
19767          "labels": [
19768            "cp1251",
19769            "windows-1251",
19770            "x-cp1251"
19771          ],
19772          "name": "windows-1251"
19773        },
19774        {
19775          "labels": [
19776            "ansi_x3.4-1968",
19777            "ascii",
19778            "cp1252",
19779            "cp819",
19780            "csisolatin1",
19781            "ibm819",
19782            "iso-8859-1",
19783            "iso-ir-100",
19784            "iso8859-1",
19785            "iso88591",
19786            "iso_8859-1",
19787            "iso_8859-1:1987",
19788            "l1",
19789            "latin1",
19790            "us-ascii",
19791            "windows-1252",
19792            "x-cp1252"
19793          ],
19794          "name": "windows-1252"
19795        },
19796        {
19797          "labels": [
19798            "cp1253",
19799            "windows-1253",
19800            "x-cp1253"
19801          ],
19802          "name": "windows-1253"
19803        },
19804        {
19805          "labels": [
19806            "cp1254",
19807            "csisolatin5",
19808            "iso-8859-9",
19809            "iso-ir-148",
19810            "iso8859-9",
19811            "iso88599",
19812            "iso_8859-9",
19813            "iso_8859-9:1989",
19814            "l5",
19815            "latin5",
19816            "windows-1254",
19817            "x-cp1254"
19818          ],
19819          "name": "windows-1254"
19820        },
19821        {
19822          "labels": [
19823            "cp1255",
19824            "windows-1255",
19825            "x-cp1255"
19826          ],
19827          "name": "windows-1255"
19828        },
19829        {
19830          "labels": [
19831            "cp1256",
19832            "windows-1256",
19833            "x-cp1256"
19834          ],
19835          "name": "windows-1256"
19836        },
19837        {
19838          "labels": [
19839            "cp1257",
19840            "windows-1257",
19841            "x-cp1257"
19842          ],
19843          "name": "windows-1257"
19844        },
19845        {
19846          "labels": [
19847            "cp1258",
19848            "windows-1258",
19849            "x-cp1258"
19850          ],
19851          "name": "windows-1258"
19852        },
19853        {
19854          "labels": [
19855            "x-mac-cyrillic",
19856            "x-mac-ukrainian"
19857          ],
19858          "name": "x-mac-cyrillic"
19859        }
19860      ],
19861      "heading": "Legacy single-byte encodings"
19862    },
19863    {
19864      "encodings": [
19865        {
19866          "labels": [
19867            "chinese",
19868            "csgb2312",
19869            "csiso58gb231280",
19870            "gb2312",
19871            "gb_2312",
19872            "gb_2312-80",
19873            "gbk",
19874            "iso-ir-58",
19875            "x-gbk"
19876          ],
19877          "name": "GBK"
19878        },
19879        {
19880          "labels": [
19881            "gb18030"
19882          ],
19883          "name": "gb18030"
19884        }
19885      ],
19886      "heading": "Legacy multi-byte Chinese (simplified) encodings"
19887    },
19888    {
19889      "encodings": [
19890        {
19891          "labels": [
19892            "big5",
19893            "big5-hkscs",
19894            "cn-big5",
19895            "csbig5",
19896            "x-x-big5"
19897          ],
19898          "name": "Big5"
19899        }
19900      ],
19901      "heading": "Legacy multi-byte Chinese (traditional) encodings"
19902    },
19903    {
19904      "encodings": [
19905        {
19906          "labels": [
19907            "cseucpkdfmtjapanese",
19908            "euc-jp",
19909            "x-euc-jp"
19910          ],
19911          "name": "EUC-JP"
19912        },
19913        {
19914          "labels": [
19915            "csiso2022jp",
19916            "iso-2022-jp"
19917          ],
19918          "name": "ISO-2022-JP"
19919        },
19920        {
19921          "labels": [
19922            "csshiftjis",
19923            "ms932",
19924            "ms_kanji",
19925            "shift-jis",
19926            "shift_jis",
19927            "sjis",
19928            "windows-31j",
19929            "x-sjis"
19930          ],
19931          "name": "Shift_JIS"
19932        }
19933      ],
19934      "heading": "Legacy multi-byte Japanese encodings"
19935    },
19936    {
19937      "encodings": [
19938        {
19939          "labels": [
19940            "cseuckr",
19941            "csksc56011987",
19942            "euc-kr",
19943            "iso-ir-149",
19944            "korean",
19945            "ks_c_5601-1987",
19946            "ks_c_5601-1989",
19947            "ksc5601",
19948            "ksc_5601",
19949            "windows-949"
19950          ],
19951          "name": "EUC-KR"
19952        }
19953      ],
19954      "heading": "Legacy multi-byte Korean encodings"
19955    },
19956    {
19957      "encodings": [
19958        {
19959          "labels": [
19960            "csiso2022kr",
19961            "hz-gb-2312",
19962            "iso-2022-cn",
19963            "iso-2022-cn-ext",
19964            "iso-2022-kr"
19965          ],
19966          "name": "replacement"
19967        },
19968        {
19969          "labels": [
19970            "utf-16be"
19971          ],
19972          "name": "UTF-16BE"
19973        },
19974        {
19975          "labels": [
19976            "utf-16",
19977            "utf-16le"
19978          ],
19979          "name": "UTF-16LE"
19980        },
19981        {
19982          "labels": [
19983            "x-user-defined"
19984          ],
19985          "name": "x-user-defined"
19986        }
19987      ],
19988      "heading": "Legacy miscellaneous encodings"
19989    }
19990  ];
19991
19992  // Label to encoding registry.
19993  /** @type {Object.<string,{name:string,labels:Array.<string>}>} */
19994  var label_to_encoding = {};
19995  encodings.forEach(function(category) {
19996    category.encodings.forEach(function(encoding) {
19997      encoding.labels.forEach(function(label) {
19998        label_to_encoding[label] = encoding;
19999      });
20000    });
20001  });
20002
20003  // Registry of of encoder/decoder factories, by encoding name.
20004  /** @type {Object.<string, function({fatal:boolean}): Encoder>} */
20005  var encoders = {};
20006  /** @type {Object.<string, function({fatal:boolean}): Decoder>} */
20007  var decoders = {};
20008
20009  //
20010  // 6. Indexes
20011  //
20012
20013  /**
20014   * @param {number} pointer The |pointer| to search for.
20015   * @param {(!Array.<?number>|undefined)} index The |index| to search within.
20016   * @return {?number} The code point corresponding to |pointer| in |index|,
20017   *     or null if |code point| is not in |index|.
20018   */
20019  function indexCodePointFor(pointer, index) {
20020    if (!index) return null;
20021    return index[pointer] || null;
20022  }
20023
20024  /**
20025   * @param {number} code_point The |code point| to search for.
20026   * @param {!Array.<?number>} index The |index| to search within.
20027   * @return {?number} The first pointer corresponding to |code point| in
20028   *     |index|, or null if |code point| is not in |index|.
20029   */
20030  function indexPointerFor(code_point, index) {
20031    var pointer = index.indexOf(code_point);
20032    return pointer === -1 ? null : pointer;
20033  }
20034
20035  /**
20036   * @param {string} name Name of the index.
20037   * @return {(!Array.<number>|!Array.<Array.<number>>)}
20038   *  */
20039  function index(name) {
20040    if (!('encoding-indexes' in global)) {
20041      throw Error("Indexes missing." +
20042                  " Did you forget to include encoding-indexes.js?");
20043    }
20044    return global['encoding-indexes'][name];
20045  }
20046
20047  /**
20048   * @param {number} pointer The |pointer| to search for in the gb18030 index.
20049   * @return {?number} The code point corresponding to |pointer| in |index|,
20050   *     or null if |code point| is not in the gb18030 index.
20051   */
20052  function indexGB18030RangesCodePointFor(pointer) {
20053    // 1. If pointer is greater than 39419 and less than 189000, or
20054    // pointer is greater than 1237575, return null.
20055    if ((pointer > 39419 && pointer < 189000) || (pointer > 1237575))
20056      return null;
20057
20058    // 2. If pointer is 7457, return code point U+E7C7.
20059    if (pointer === 7457) return 0xE7C7;
20060
20061    // 3. Let offset be the last pointer in index gb18030 ranges that
20062    // is equal to or less than pointer and let code point offset be
20063    // its corresponding code point.
20064    var offset = 0;
20065    var code_point_offset = 0;
20066    var idx = index('gb18030');
20067    var i;
20068    for (i = 0; i < idx.length; ++i) {
20069      /** @type {!Array.<number>} */
20070      var entry = idx[i];
20071      if (entry[0] <= pointer) {
20072        offset = entry[0];
20073        code_point_offset = entry[1];
20074      } else {
20075        break;
20076      }
20077    }
20078
20079    // 4. Return a code point whose value is code point offset +
20080    // pointer − offset.
20081    return code_point_offset + pointer - offset;
20082  }
20083
20084  /**
20085   * @param {number} code_point The |code point| to locate in the gb18030 index.
20086   * @return {number} The first pointer corresponding to |code point| in the
20087   *     gb18030 index.
20088   */
20089  function indexGB18030RangesPointerFor(code_point) {
20090    // 1. If code point is U+E7C7, return pointer 7457.
20091    if (code_point === 0xE7C7) return 7457;
20092
20093    // 2. Let offset be the last code point in index gb18030 ranges
20094    // that is equal to or less than code point and let pointer offset
20095    // be its corresponding pointer.
20096    var offset = 0;
20097    var pointer_offset = 0;
20098    var idx = index('gb18030');
20099    var i;
20100    for (i = 0; i < idx.length; ++i) {
20101      /** @type {!Array.<number>} */
20102      var entry = idx[i];
20103      if (entry[1] <= code_point) {
20104        offset = entry[1];
20105        pointer_offset = entry[0];
20106      } else {
20107        break;
20108      }
20109    }
20110
20111    // 3. Return a pointer whose value is pointer offset + code point
20112    // − offset.
20113    return pointer_offset + code_point - offset;
20114  }
20115
20116  /**
20117   * @param {number} code_point The |code_point| to search for in the shift_jis
20118   *     index.
20119   * @return {?number} The code point corresponding to |pointer| in |index|,
20120   *     or null if |code point| is not in the shift_jis index.
20121   */
20122  function indexShiftJISPointerFor(code_point) {
20123    // 1. Let index be index jis0208 excluding all pointers in the
20124    // range 8272 to 8835.
20125    var pointer = indexPointerFor(code_point, index('jis0208'));
20126    if (pointer === null || inRange(pointer, 8272, 8835))
20127      return null;
20128
20129    // 2. Return the index pointer for code point in index.
20130    return pointer;
20131  }
20132
20133  /**
20134   * @param {number} code_point The |code_point| to search for in the big5
20135   *     index.
20136   * @return {?number} The code point corresponding to |pointer| in |index|,
20137   *     or null if |code point| is not in the big5 index.
20138   */
20139  function indexBig5PointerFor(code_point) {
20140
20141    // 1. Let index be index big5.
20142    var index_ = index('big5');
20143
20144    // 2. If code point is U+2550, U+255E, U+2561, U+256A, U+5341, or
20145    // U+5345, return the last pointer corresponding to code point in
20146    // index.
20147    if (code_point === 0x2550 || code_point === 0x255E ||
20148        code_point === 0x2561 || code_point === 0x256A ||
20149        code_point === 0x5341 || code_point === 0x5345) {
20150      return index_.lastIndexOf(code_point);
20151    }
20152
20153    // 3. Return the index pointer for code point in index.
20154    return indexPointerFor(code_point, index_);
20155  }
20156
20157  //
20158  // 8. API
20159  //
20160
20161  /** @const */ var DEFAULT_ENCODING = 'utf-8';
20162
20163  // 8.1 Interface TextDecoder
20164
20165  /**
20166   * @constructor
20167   * @param {string=} label The label of the encoding;
20168   *     defaults to 'utf-8'.
20169   * @param {Object=} options
20170   */
20171  function TextDecoder(label, options) {
20172    // Web IDL conventions
20173    if (!(this instanceof TextDecoder))
20174      throw TypeError('Called as a function. Did you forget \'new\'?');
20175    label = label !== undefined ? String(label) : DEFAULT_ENCODING;
20176    options = ToDictionary(options);
20177
20178    // A TextDecoder object has an associated encoding, decoder,
20179    // stream, ignore BOM flag (initially unset), BOM seen flag
20180    // (initially unset), error mode (initially replacement), and do
20181    // not flush flag (initially unset).
20182
20183    /** @private */
20184    this._encoding = null;
20185    /** @private @type {?Decoder} */
20186    this._decoder = null;
20187    /** @private @type {boolean} */
20188    this._ignoreBOM = false;
20189    /** @private @type {boolean} */
20190    this._BOMseen = false;
20191    /** @private @type {string} */
20192    this._error_mode = 'replacement';
20193    /** @private @type {boolean} */
20194    this._do_not_flush = false;
20195
20196
20197    // 1. Let encoding be the result of getting an encoding from
20198    // label.
20199    var encoding = getEncoding(label);
20200
20201    // 2. If encoding is failure or replacement, throw a RangeError.
20202    if (encoding === null || encoding.name === 'replacement')
20203      throw RangeError('Unknown encoding: ' + label);
20204    if (!decoders[encoding.name]) {
20205      throw Error('Decoder not present.' +
20206                  ' Did you forget to include encoding-indexes.js?');
20207    }
20208
20209    // 3. Let dec be a new TextDecoder object.
20210    var dec = this;
20211
20212    // 4. Set dec's encoding to encoding.
20213    dec._encoding = encoding;
20214
20215    // 5. If options's fatal member is true, set dec's error mode to
20216    // fatal.
20217    if (Boolean(options['fatal']))
20218      dec._error_mode = 'fatal';
20219
20220    // 6. If options's ignoreBOM member is true, set dec's ignore BOM
20221    // flag.
20222    if (Boolean(options['ignoreBOM']))
20223      dec._ignoreBOM = true;
20224
20225    // For pre-ES5 runtimes:
20226    if (!Object.defineProperty) {
20227      this.encoding = dec._encoding.name.toLowerCase();
20228      this.fatal = dec._error_mode === 'fatal';
20229      this.ignoreBOM = dec._ignoreBOM;
20230    }
20231
20232    // 7. Return dec.
20233    return dec;
20234  }
20235
20236  if (Object.defineProperty) {
20237    // The encoding attribute's getter must return encoding's name.
20238    Object.defineProperty(TextDecoder.prototype, 'encoding', {
20239      /** @this {TextDecoder} */
20240      get: function() { return this._encoding.name.toLowerCase(); }
20241    });
20242
20243    // The fatal attribute's getter must return true if error mode
20244    // is fatal, and false otherwise.
20245    Object.defineProperty(TextDecoder.prototype, 'fatal', {
20246      /** @this {TextDecoder} */
20247      get: function() { return this._error_mode === 'fatal'; }
20248    });
20249
20250    // The ignoreBOM attribute's getter must return true if ignore
20251    // BOM flag is set, and false otherwise.
20252    Object.defineProperty(TextDecoder.prototype, 'ignoreBOM', {
20253      /** @this {TextDecoder} */
20254      get: function() { return this._ignoreBOM; }
20255    });
20256  }
20257
20258  /**
20259   * @param {BufferSource=} input The buffer of bytes to decode.
20260   * @param {Object=} options
20261   * @return {string} The decoded string.
20262   */
20263  TextDecoder.prototype.decode = function decode(input, options) {
20264    var bytes;
20265    if (typeof input === 'object' && input instanceof ArrayBuffer) {
20266      bytes = new Uint8Array(input);
20267    } else if (typeof input === 'object' && 'buffer' in input &&
20268               input.buffer instanceof ArrayBuffer) {
20269      bytes = new Uint8Array(input.buffer,
20270                             input.byteOffset,
20271                             input.byteLength);
20272    } else {
20273      bytes = new Uint8Array(0);
20274    }
20275
20276    options = ToDictionary(options);
20277
20278    // 1. If the do not flush flag is unset, set decoder to a new
20279    // encoding's decoder, set stream to a new stream, and unset the
20280    // BOM seen flag.
20281    if (!this._do_not_flush) {
20282      this._decoder = decoders[this._encoding.name]({
20283        fatal: this._error_mode === 'fatal'});
20284      this._BOMseen = false;
20285    }
20286
20287    // 2. If options's stream is true, set the do not flush flag, and
20288    // unset the do not flush flag otherwise.
20289    this._do_not_flush = Boolean(options['stream']);
20290
20291    // 3. If input is given, push a copy of input to stream.
20292    // TODO: Align with spec algorithm - maintain stream on instance.
20293    var input_stream = new Stream(bytes);
20294
20295    // 4. Let output be a new stream.
20296    var output = [];
20297
20298    /** @type {?(number|!Array.<number>)} */
20299    var result;
20300
20301    // 5. While true:
20302    while (true) {
20303      // 1. Let token be the result of reading from stream.
20304      var token = input_stream.read();
20305
20306      // 2. If token is end-of-stream and the do not flush flag is
20307      // set, return output, serialized.
20308      // TODO: Align with spec algorithm.
20309      if (token === end_of_stream)
20310        break;
20311
20312      // 3. Otherwise, run these subsubsteps:
20313
20314      // 1. Let result be the result of processing token for decoder,
20315      // stream, output, and error mode.
20316      result = this._decoder.handler(input_stream, token);
20317
20318      // 2. If result is finished, return output, serialized.
20319      if (result === finished)
20320        break;
20321
20322      if (result !== null) {
20323        if (Array.isArray(result))
20324          output.push.apply(output, /**@type {!Array.<number>}*/(result));
20325        else
20326          output.push(result);
20327      }
20328
20329      // 3. Otherwise, if result is error, throw a TypeError.
20330      // (Thrown in handler)
20331
20332      // 4. Otherwise, do nothing.
20333    }
20334    // TODO: Align with spec algorithm.
20335    if (!this._do_not_flush) {
20336      do {
20337        result = this._decoder.handler(input_stream, input_stream.read());
20338        if (result === finished)
20339          break;
20340        if (result === null)
20341          continue;
20342        if (Array.isArray(result))
20343          output.push.apply(output, /**@type {!Array.<number>}*/(result));
20344        else
20345          output.push(result);
20346      } while (!input_stream.endOfStream());
20347      this._decoder = null;
20348    }
20349
20350    // A TextDecoder object also has an associated serialize stream
20351    // algorithm...
20352    /**
20353     * @param {!Array.<number>} stream
20354     * @return {string}
20355     * @this {TextDecoder}
20356     */
20357    function serializeStream(stream) {
20358      // 1. Let token be the result of reading from stream.
20359      // (Done in-place on array, rather than as a stream)
20360
20361      // 2. If encoding is UTF-8, UTF-16BE, or UTF-16LE, and ignore
20362      // BOM flag and BOM seen flag are unset, run these subsubsteps:
20363      if (includes(['UTF-8', 'UTF-16LE', 'UTF-16BE'], this._encoding.name) &&
20364          !this._ignoreBOM && !this._BOMseen) {
20365        if (stream.length > 0 && stream[0] === 0xFEFF) {
20366          // 1. If token is U+FEFF, set BOM seen flag.
20367          this._BOMseen = true;
20368          stream.shift();
20369        } else if (stream.length > 0) {
20370          // 2. Otherwise, if token is not end-of-stream, set BOM seen
20371          // flag and append token to stream.
20372          this._BOMseen = true;
20373        } else {
20374          // 3. Otherwise, if token is not end-of-stream, append token
20375          // to output.
20376          // (no-op)
20377        }
20378      }
20379      // 4. Otherwise, return output.
20380      return codePointsToString(stream);
20381    }
20382
20383    return serializeStream.call(this, output);
20384  };
20385
20386  // 8.2 Interface TextEncoder
20387
20388  /**
20389   * @constructor
20390   * @param {string=} label The label of the encoding. NONSTANDARD.
20391   * @param {Object=} options NONSTANDARD.
20392   */
20393  function TextEncoder(label, options) {
20394    // Web IDL conventions
20395    if (!(this instanceof TextEncoder))
20396      throw TypeError('Called as a function. Did you forget \'new\'?');
20397    options = ToDictionary(options);
20398
20399    // A TextEncoder object has an associated encoding and encoder.
20400
20401    /** @private */
20402    this._encoding = null;
20403    /** @private @type {?Encoder} */
20404    this._encoder = null;
20405
20406    // Non-standard
20407    /** @private @type {boolean} */
20408    this._do_not_flush = false;
20409    /** @private @type {string} */
20410    this._fatal = Boolean(options['fatal']) ? 'fatal' : 'replacement';
20411
20412    // 1. Let enc be a new TextEncoder object.
20413    var enc = this;
20414
20415    // 2. Set enc's encoding to UTF-8's encoder.
20416    if (Boolean(options['NONSTANDARD_allowLegacyEncoding'])) {
20417      // NONSTANDARD behavior.
20418      label = label !== undefined ? String(label) : DEFAULT_ENCODING;
20419      var encoding = getEncoding(label);
20420      if (encoding === null || encoding.name === 'replacement')
20421        throw RangeError('Unknown encoding: ' + label);
20422      if (!encoders[encoding.name]) {
20423        throw Error('Encoder not present.' +
20424                    ' Did you forget to include encoding-indexes.js?');
20425      }
20426      enc._encoding = encoding;
20427    } else {
20428      // Standard behavior.
20429      enc._encoding = getEncoding('utf-8');
20430
20431      if (label !== undefined && 'console' in global) {
20432        console.warn('TextEncoder constructor called with encoding label, '
20433                     + 'which is ignored.');
20434      }
20435    }
20436
20437    // For pre-ES5 runtimes:
20438    if (!Object.defineProperty)
20439      this.encoding = enc._encoding.name.toLowerCase();
20440
20441    // 3. Return enc.
20442    return enc;
20443  }
20444
20445  if (Object.defineProperty) {
20446    // The encoding attribute's getter must return encoding's name.
20447    Object.defineProperty(TextEncoder.prototype, 'encoding', {
20448      /** @this {TextEncoder} */
20449      get: function() { return this._encoding.name.toLowerCase(); }
20450    });
20451  }
20452
20453  /**
20454   * @param {string=} opt_string The string to encode.
20455   * @param {Object=} options
20456   * @return {!Uint8Array} Encoded bytes, as a Uint8Array.
20457   */
20458  TextEncoder.prototype.encode = function encode(opt_string, options) {
20459    opt_string = opt_string ? String(opt_string) : '';
20460    options = ToDictionary(options);
20461
20462    // NOTE: This option is nonstandard. None of the encodings
20463    // permitted for encoding (i.e. UTF-8, UTF-16) are stateful when
20464    // the input is a USVString so streaming is not necessary.
20465    if (!this._do_not_flush)
20466      this._encoder = encoders[this._encoding.name]({
20467        fatal: this._fatal === 'fatal'});
20468    this._do_not_flush = Boolean(options['stream']);
20469
20470    // 1. Convert input to a stream.
20471    var input = new Stream(stringToCodePoints(opt_string));
20472
20473    // 2. Let output be a new stream
20474    var output = [];
20475
20476    /** @type {?(number|!Array.<number>)} */
20477    var result;
20478    // 3. While true, run these substeps:
20479    while (true) {
20480      // 1. Let token be the result of reading from input.
20481      var token = input.read();
20482      if (token === end_of_stream)
20483        break;
20484      // 2. Let result be the result of processing token for encoder,
20485      // input, output.
20486      result = this._encoder.handler(input, token);
20487      if (result === finished)
20488        break;
20489      if (Array.isArray(result))
20490        output.push.apply(output, /**@type {!Array.<number>}*/(result));
20491      else
20492        output.push(result);
20493    }
20494    // TODO: Align with spec algorithm.
20495    if (!this._do_not_flush) {
20496      while (true) {
20497        result = this._encoder.handler(input, input.read());
20498        if (result === finished)
20499          break;
20500        if (Array.isArray(result))
20501          output.push.apply(output, /**@type {!Array.<number>}*/(result));
20502        else
20503          output.push(result);
20504      }
20505      this._encoder = null;
20506    }
20507    // 3. If result is finished, convert output into a byte sequence,
20508    // and then return a Uint8Array object wrapping an ArrayBuffer
20509    // containing output.
20510    return new Uint8Array(output);
20511  };
20512
20513
20514  //
20515  // 9. The encoding
20516  //
20517
20518  // 9.1 utf-8
20519
20520  // 9.1.1 utf-8 decoder
20521  /**
20522   * @constructor
20523   * @implements {Decoder}
20524   * @param {{fatal: boolean}} options
20525   */
20526  function UTF8Decoder(options) {
20527    var fatal = options.fatal;
20528
20529    // utf-8's decoder's has an associated utf-8 code point, utf-8
20530    // bytes seen, and utf-8 bytes needed (all initially 0), a utf-8
20531    // lower boundary (initially 0x80), and a utf-8 upper boundary
20532    // (initially 0xBF).
20533    var /** @type {number} */ utf8_code_point = 0,
20534        /** @type {number} */ utf8_bytes_seen = 0,
20535        /** @type {number}
20535 */ utf8_bytes_needed = 0,
20536        /** @type {number} */ utf8_lower_boundary = 0x80,
20537        /** @type {number} */ utf8_upper_boundary = 0xBF;
20538
20539    /**
20540     * @param {Stream} stream The stream of bytes being decoded.
20541     * @param {number} bite The next byte read from the stream.
20542     * @return {?(number|!Array.<number>)} The next code point(s)
20543     *     decoded, or null if not enough data exists in the input
20544     *     stream to decode a complete code point.
20545     */
20546    this.handler = function(stream, bite) {
20547      // 1. If byte is end-of-stream and utf-8 bytes needed is not 0,
20548      // set utf-8 bytes needed to 0 and return error.
20549      if (bite === end_of_stream && utf8_bytes_needed !== 0) {
20550        utf8_bytes_needed = 0;
20551        return decoderError(fatal);
20552      }
20553
20554      // 2. If byte is end-of-stream, return finished.
20555      if (bite === end_of_stream)
20556        return finished;
20557
20558      // 3. If utf-8 bytes needed is 0, based on byte:
20559      if (utf8_bytes_needed === 0) {
20560
20561        // 0x00 to 0x7F
20562        if (inRange(bite, 0x00, 0x7F)) {
20563          // Return a code point whose value is byte.
20564          return bite;
20565        }
20566
20567        // 0xC2 to 0xDF
20568        if (inRange(bite, 0xC2, 0xDF)) {
20569          // Set utf-8 bytes needed to 1 and utf-8 code point to byte
20570          // − 0xC0.
20571          utf8_bytes_needed = 1;
20572          utf8_code_point = bite - 0xC0;
20573        }
20574
20575        // 0xE0 to 0xEF
20576        else if (inRange(bite, 0xE0, 0xEF)) {
20577          // 1. If byte is 0xE0, set utf-8 lower boundary to 0xA0.
20578          if (bite === 0xE0)
20579            utf8_lower_boundary = 0xA0;
20580          // 2. If byte is 0xED, set utf-8 upper boundary to 0x9F.
20581          if (bite === 0xED)
20582            utf8_upper_boundary = 0x9F;
20583          // 3. Set utf-8 bytes needed to 2 and utf-8 code point to
20584          // byte − 0xE0.
20585          utf8_bytes_needed = 2;
20586          utf8_code_point = bite - 0xE0;
20587        }
20588
20589        // 0xF0 to 0xF4
20590        else if (inRange(bite, 0xF0, 0xF4)) {
20591          // 1. If byte is 0xF0, set utf-8 lower boundary to 0x90.
20592          if (bite === 0xF0)
20593            utf8_lower_boundary = 0x90;
20594          // 2. If byte is 0xF4, set utf-8 upper boundary to 0x8F.
20595          if (bite === 0xF4)
20596            utf8_upper_boundary = 0x8F;
20597          // 3. Set utf-8 bytes needed to 3 and utf-8 code point to
20598          // byte − 0xF0.
20599          utf8_bytes_needed = 3;
20600          utf8_code_point = bite - 0xF0;
20601        }
20602
20603        // Otherwise
20604        else {
20605          // Return error.
20606          return decoderError(fatal);
20607        }
20608
20609        // Then (byte is in the range 0xC2 to 0xF4, inclusive) set
20610        // utf-8 code point to utf-8 code point << (6 × utf-8 bytes
20611        // needed) and return continue.
20612        utf8_code_point = utf8_code_point << (6 * utf8_bytes_needed);
20613        return null;
20614      }
20615
20616      // 4. If byte is not in the range utf-8 lower boundary to utf-8
20617      // upper boundary, inclusive, run these substeps:
20618      if (!inRange(bite, utf8_lower_boundary, utf8_upper_boundary)) {
20619
20620        // 1. Set utf-8 code point, utf-8 bytes needed, and utf-8
20621        // bytes seen to 0, set utf-8 lower boundary to 0x80, and set
20622        // utf-8 upper boundary to 0xBF.
20623        utf8_code_point = utf8_bytes_needed = utf8_bytes_seen = 0;
20624        utf8_lower_boundary = 0x80;
20625        utf8_upper_boundary = 0xBF;
20626
20627        // 2. Prepend byte to stream.
20628        stream.prepend(bite);
20629
20630        // 3. Return error.
20631        return decoderError(fatal);
20632      }
20633
20634      // 5. Set utf-8 lower boundary to 0x80 and utf-8 upper boundary
20635      // to 0xBF.
20636      utf8_lower_boundary = 0x80;
20637      utf8_upper_boundary = 0xBF;
20638
20639      // 6. Increase utf-8 bytes seen by one and set utf-8 code point
20640      // to utf-8 code point + (byte − 0x80) << (6 × (utf-8 bytes
20641      // needed − utf-8 bytes seen)).
20642      utf8_bytes_seen += 1;
20643      utf8_code_point += (bite - 0x80) << (6 * (utf8_bytes_needed -
20644                                                utf8_bytes_seen));
20645
20646      // 7. If utf-8 bytes seen is not equal to utf-8 bytes needed,
20647      // continue.
20648      if (utf8_bytes_seen !== utf8_bytes_needed)
20649        return null;
20650
20651      // 8. Let code point be utf-8 code point.
20652      var code_point = utf8_code_point;
20653
20654      // 9. Set utf-8 code point, utf-8 bytes needed, and utf-8 bytes
20655      // seen to 0.
20656      utf8_code_point = utf8_bytes_needed = utf8_bytes_seen = 0;
20657
20658      // 10. Return a code point whose value is code point.
20659      return code_point;
20660    };
20661  }
20662
20663  // 9.1.2 utf-8 encoder
20664  /**
20665   * @constructor
20666   * @implements {Encoder}
20667   * @param {{fatal: boolean}} options
20668   */
20669  function UTF8Encoder(options) {
20670    var fatal = options.fatal;
20671    /**
20672     * @param {Stream} stream Input stream.
20673     * @param {number} code_point Next code point read from the stream.
20674     * @return {(number|!Array.<number>)} Byte(s) to emit.
20675     */
20676    this.handler = function(stream, code_point) {
20677      // 1. If code point is end-of-stream, return finished.
20678      if (code_point === end_of_stream)
20679        return finished;
20680
20681      // 2. If code point is in the range U+0000 to U+007F, return a
20682      // byte whose value is code point.
20683      if (inRange(code_point, 0x0000, 0x007f))
20684        return code_point;
20685
20686      // 3. Set count and offset based on the range code point is in:
20687      var count, offset;
20688      // U+0080 to U+07FF, inclusive:
20689      if (inRange(code_point, 0x0080, 0x07FF)) {
20690        // 1 and 0xC0
20691        count = 1;
20692        offset = 0xC0;
20693      }
20694      // U+0800 to U+FFFF, inclusive:
20695      else if (inRange(code_point, 0x0800, 0xFFFF)) {
20696        // 2 and 0xE0
20697        count = 2;
20698        offset = 0xE0;
20699      }
20700      // U+10000 to U+10FFFF, inclusive:
20701      else if (inRange(code_point, 0x10000, 0x10FFFF)) {
20702        // 3 and 0xF0
20703        count = 3;
20704        offset = 0xF0;
20705      }
20706
20707      // 4.Let bytes be a byte sequence whose first byte is (code
20708      // point >> (6 × count)) + offset.
20709      var bytes = [(code_point >> (6 * count)) + offset];
20710
20711      // 5. Run these substeps while count is greater than 0:
20712      while (count > 0) {
20713
20714        // 1. Set temp to code point >> (6 × (count − 1)).
20715        var temp = code_point >> (6 * (count - 1));
20716
20717        // 2. Append to bytes 0x80 | (temp & 0x3F).
20718        bytes.push(0x80 | (temp & 0x3F));
20719
20720        // 3. Decrease count by one.
20721        count -= 1;
20722      }
20723
20724      // 6. Return bytes bytes, in order.
20725      return bytes;
20726    };
20727  }
20728
20729  /** @param {{fatal: boolean}} options */
20730  encoders['UTF-8'] = function(options) {
20731    return new UTF8Encoder(options);
20732  };
20733  /** @param {{fatal: boolean}} options */
20734  decoders['UTF-8'] = function(options) {
20735    return new UTF8Decoder(options);
20736  };
20737
20738  //
20739  // 10. Legacy single-byte encodings
20740  //
20741
20742  // 10.1 single-byte decoder
20743  /**
20744   * @constructor
20745   * @implements {Decoder}
20746   * @param {!Array.<number>} index The encoding index.
20747   * @param {{fatal: boolean}} options
20748   */
20749  function SingleByteDecoder(index, options) {
20750    var fatal = options.fatal;
20751    /**
20752     * @param {Stream} stream The stream of bytes being decoded.
20753     * @param {number} bite The next byte read from the stream.
20754     * @return {?(number|!Array.<number>)} The next code point(s)
20755     *     decoded, or null if not enough data exists in the input
20756     *     stream to decode a complete code point.
20757     */
20758    this.handler = function(stream, bite) {
20759      // 1. If byte is end-of-stream, return finished.
20760      if (bite === end_of_stream)
20761        return finished;
20762
20763      // 2. If byte is an ASCII byte, return a code point whose value
20764      // is byte.
20765      if (isASCIIByte(bite))
20766        return bite;
20767
20768      // 3. Let code point be the index code point for byte − 0x80 in
20769      // index single-byte.
20770      var code_point = index[bite - 0x80];
20771
20772      // 4. If code point is null, return error.
20773      if (code_point === null)
20774        return decoderError(fatal);
20775
20776      // 5. Return a code point whose value is code point.
20777      return code_point;
20778    };
20779  }
20780
20781  // 10.2 single-byte encoder
20782  /**
20783   * @constructor
20784   * @implements {Encoder}
20785   * @param {!Array.<?number>} index The encoding index.
20786   * @param {{fatal: boolean}} options
20787   */
20788  function SingleByteEncoder(index, options) {
20789    var fatal = options.fatal;
20790    /**
20791     * @param {Stream} stream Input stream.
20792     * @param {number} code_point Next code point read from the stream.
20793     * @return {(number|!Array.<number>)} Byte(s) to emit.
20794     */
20795    this.handler = function(stream, code_point) {
20796      // 1. If code point is end-of-stream, return finished.
20797      if (code_point === end_of_stream)
20798        return finished;
20799
20800      // 2. If code point is an ASCII code point, return a byte whose
20801      // value is code point.
20802      if (isASCIICodePoint(code_point))
20803        return code_point;
20804
20805      // 3. Let pointer be the index pointer for code point in index
20806      // single-byte.
20807      var pointer = indexPointerFor(code_point, index);
20808
20809      // 4. If pointer is null, return error with code point.
20810      if (pointer === null)
20811        encoderError(code_point);
20812
20813      // 5. Return a byte whose value is pointer + 0x80.
20814      return pointer + 0x80;
20815    };
20816  }
20817
20818  (function() {
20819    if (!('encoding-indexes' in global))
20820      return;
20821    encodings.forEach(function(category) {
20822      if (category.heading !== 'Legacy single-byte encodings')
20823        return;
20824      category.encodings.forEach(function(encoding) {
20825        var name = encoding.name;
20826        var idx = index(name.toLowerCase());
20827        /** @param {{fatal: boolean}} options */
20828        decoders[name] = function(options) {
20829          return new SingleByteDecoder(idx, options);
20830        };
20831        /** @param {{fatal: boolean}} options */
20832        encoders[name] = function(options) {
20833          return new SingleByteEncoder(idx, options);
20834        };
20835      });
20836    });
20837  }());
20838
20839  //
20840  // 11. Legacy multi-byte Chinese (simplified) encodings
20841  //
20842
20843  // 11.1 gbk
20844
20845  // 11.1.1 gbk decoder
20846  // gbk's decoder is gb18030's decoder.
20847  /** @param {{fatal: boolean}} options */
20848  decoders['GBK'] = function(options) {
20849    return new GB18030Decoder(options);
20850  };
20851
20852  // 11.1.2 gbk encoder
20853  // gbk's encoder is gb18030's encoder with its gbk flag set.
20854  /** @param {{fatal: boolean}} options */
20855  encoders['GBK'] = function(options) {
20856    return new GB18030Encoder(options, true);
20857  };
20858
20859  // 11.2 gb18030
20860
20861  // 11.2.1 gb18030 decoder
20862  /**
20863   * @constructor
20864   * @implements {Decoder}
20865   * @param {{fatal: boolean}} options
20866   */
20867  function GB18030Decoder(options) {
20868    var fatal = options.fatal;
20869    // gb18030's decoder has an associated gb18030 first, gb18030
20870    // second, and gb18030 third (all initially 0x00).
20871    var /** @type {number} */ gb18030_first = 0x00,
20872        /** @type {number} */ gb18030_second = 0x00,
20873        /** @type {number} */ gb18030_third = 0x00;
20874    /**
20875     * @param {Stream} stream The stream of bytes being decoded.
20876     * @param {number} bite The next byte read from the stream.
20877     * @return {?(number|!Array.<number>)} The next code point(s)
20878     *     decoded, or null if not enough data exists in the input
20879     *     stream to decode a complete code point.
20880     */
20881    this.handler = function(stream, bite) {
20882      // 1. If byte is end-of-stream and gb18030 first, gb18030
20883      // second, and gb18030 third are 0x00, return finished.
20884      if (bite === end_of_stream && gb18030_first === 0x00 &&
20885          gb18030_second === 0x00 && gb18030_third === 0x00) {
20886        return finished;
20887      }
20888      // 2. If byte is end-of-stream, and gb18030 first, gb18030
20889      // second, or gb18030 third is not 0x00, set gb18030 first,
20890      // gb18030 second, and gb18030 third to 0x00, and return error.
20891      if (bite === end_of_stream &&
20892          (gb18030_first !== 0x00 || gb18030_second !== 0x00 ||
20893           gb18030_third !== 0x00)) {
20894        gb18030_first = 0x00;
20895        gb18030_second = 0x00;
20896        gb18030_third = 0x00;
20897        decoderError(fatal);
20898      }
20899      var code_point;
20900      // 3. If gb18030 third is not 0x00, run these substeps:
20901      if (gb18030_third !== 0x00) {
20902        // 1. Let code point be null.
20903        code_point = null;
20904        // 2. If byte is in the range 0x30 to 0x39, set code point to
20905        // the index gb18030 ranges code point for (((gb18030 first −
20906        // 0x81) × 10 + gb18030 second − 0x30) × 126 + gb18030 third −
20907        // 0x81) × 10 + byte − 0x30.
20908        if (inRange(bite, 0x30, 0x39)) {
20909          code_point = indexGB18030RangesCodePointFor(
20910              (((gb18030_first - 0x81) * 10 + (gb18030_second - 0x30)) * 126 +
20911               (gb18030_third - 0x81)) * 10 + bite - 0x30);
20912        }
20913
20914        // 3. Let buffer be a byte sequence consisting of gb18030
20915        // second, gb18030 third, and byte, in order.
20916        var buffer = [gb18030_second, gb18030_third, bite];
20917
20918        // 4. Set gb18030 first, gb18030 second, and gb18030 third to
20919        // 0x00.
20920        gb18030_first = 0x00;
20921        gb18030_second = 0x00;
20922        gb18030_third = 0x00;
20923
20924        // 5. If code point is null, prepend buffer to stream and
20925        // return error.
20926        if (code_point === null) {
20927          stream.prepend(buffer);
20928          return decoderError(fatal);
20929        }
20930
20931        // 6. Return a code point whose value is code point.
20932        return code_point;
20933      }
20934
20935      // 4. If gb18030 second is not 0x00, run these substeps:
20936      if (gb18030_second !== 0x00) {
20937
20938        // 1. If byte is in the range 0x81 to 0xFE, set gb18030 third
20939        // to byte and return continue.
20940        if (inRange(bite, 0x81, 0xFE)) {
20941          gb18030_third = bite;
20942          return null;
20943        }
20944
20945        // 2. Prepend gb18030 second followed by byte to stream, set
20946        // gb18030 first and gb18030 second to 0x00, and return error.
20947        stream.prepend([gb18030_second, bite]);
20948        gb18030_first = 0x00;
20949        gb18030_second = 0x00;
20950        return decoderError(fatal);
20951      }
20952
20953      // 5. If gb18030 first is not 0x00, run these substeps:
20954      if (gb18030_first !== 0x00) {
20955
20956        // 1. If byte is in the range 0x30 to 0x39, set gb18030 second
20957        // to byte and return continue.
20958        if (inRange(bite, 0x30, 0x39)) {
20959          gb18030_second = bite;
20960          return null;
20961        }
20962
20963        // 2. Let lead be gb18030 first, let pointer be null, and set
20964        // gb18030 first to 0x00.
20965        var lead = gb18030_first;
20966        var pointer = null;
20967        gb18030_first = 0x00;
20968
20969        // 3. Let offset be 0x40 if byte is less than 0x7F and 0x41
20970        // otherwise.
20971        var offset = bite < 0x7F ? 0x40 : 0x41;
20972
20973        // 4. If byte is in the range 0x40 to 0x7E or 0x80 to 0xFE,
20974        // set pointer to (lead − 0x81) × 190 + (byte − offset).
20975        if (inRange(bite, 0x40, 0x7E) || inRange(bite, 0x80, 0xFE))
20976          pointer = (lead - 0x81) * 190 + (bite - offset);
20977
20978        // 5. Let code point be null if pointer is null and the index
20979        // code point for pointer in index gb18030 otherwise.
20980        code_point = pointer === null ? null :
20981            indexCodePointFor(pointer, index('gb18030'));
20982
20983        // 6. If code point is null and byte is an ASCII byte, prepend
20984        // byte to stream.
20985        if (code_point === null && isASCIIByte(bite))
20986          stream.prepend(bite);
20987
20988        // 7. If code point is null, return error.
20989        if (code_point === null)
20990          return decoderError(fatal);
20991
20992        // 8. Return a code point whose value is code point.
20993        return code_point;
20994      }
20995
20996      // 6. If byte is an ASCII byte, return a code point whose value
20997      // is byte.
20998      if (isASCIIByte(bite))
20999        return bite;
21000
21001      // 7. If byte is 0x80, return code point U+20AC.
21002      if (bite === 0x80)
21003        return 0x20AC;
21004
21005      // 8. If byte is in the range 0x81 to 0xFE, set gb18030 first to
21006      // byte and return continue.
21007      if (inRange(bite, 0x81, 0xFE)) {
21008        gb18030_first = bite;
21009        return null;
21010      }
21011
21012      // 9. Return error.
21013      return decoderError(fatal);
21014    };
21015  }
21016
21017  // 11.2.2 gb18030 encoder
21018  /**
21019   * @constructor
21020   * @implements {Encoder}
21021   * @param {{fatal: boolean}} options
21022   * @param {boolean=} gbk_flag
21023   */
21024  function GB18030Encoder(options, gbk_flag) {
21025    var fatal = options.fatal;
21026    // gb18030's decoder has an associated gbk flag (initially unset).
21027    /**
21028     * @param {Stream} stream Input stream.
21029     * @param {number} code_point Next code point read from the stream.
21030     * @return {(number|!Array.<number>)} Byte(s) to emit.
21031     */
21032    this.handler = function(stream, code_point) {
21033      // 1. If code point is end-of-stream, return finished.
21034      if (code_point === end_of_stream)
21035        return finished;
21036
21037      // 2. If code point is an ASCII code point, return a byte whose
21038      // value is code point.
21039      if (isASCIICodePoint(code_point))
21040        return code_point;
21041
21042      // 3. If code point is U+E5E5, return error with code point.
21043      if (code_point === 0xE5E5)
21044        return encoderError(code_point);
21045
21046      // 4. If the gbk flag is set and code point is U+20AC, return
21047      // byte 0x80.
21048      if (gbk_flag && code_point === 0x20AC)
21049        return 0x80;
21050
21051      // 5. Let pointer be the index pointer for code point in index
21052      // gb18030.
21053      var pointer = indexPointerFor(code_point, index('gb18030'));
21054
21055      // 6. If pointer is not null, run these substeps:
21056      if (pointer !== null) {
21057
21058        // 1. Let lead be floor(pointer / 190) + 0x81.
21059        var lead = floor(pointer / 190) + 0x81;
21060
21061        // 2. Let trail be pointer % 190.
21062        var trail = pointer % 190;
21063
21064        // 3. Let offset be 0x40 if trail is less than 0x3F and 0x41 otherwise.
21065        var offset = trail < 0x3F ? 0x40 : 0x41;
21066
21067        // 4. Return two bytes whose values are lead and trail + offset.
21068        return [lead, trail + offset];
21069      }
21070
21071      // 7. If gbk flag is set, return error with code point.
21072      if (gbk_flag)
21073        return encoderError(code_point);
21074
21075      // 8. Set pointer to the index gb18030 ranges pointer for code
21076      // point.
21077      pointer = indexGB18030RangesPointerFor(code_point);
21078
21079      // 9. Let byte1 be floor(pointer / 10 / 126 / 10).
21080      var byte1 = floor(pointer / 10 / 126 / 10);
21081
21082      // 10. Set pointer to pointer − byte1 × 10 × 126 × 10.
21083      pointer = pointer - byte1 * 10 * 126 * 10;
21084
21085      // 11. Let byte2 be floor(pointer / 10 / 126).
21086      var byte2 = floor(pointer / 10 / 126);
21087
21088      // 12. Set pointer to pointer − byte2 × 10 × 126.
21089      pointer = pointer - byte2 * 10 * 126;
21090
21091      // 13. Let byte3 be floor(pointer / 10).
21092      var byte3 = floor(pointer / 10);
21093
21094      // 14. Let byte4 be pointer − byte3 × 10.
21095      var byte4 = pointer - byte3 * 10;
21096
21097      // 15. Return four bytes whose values are byte1 + 0x81, byte2 +
21098      // 0x30, byte3 + 0x81, byte4 + 0x30.
21099      return [byte1 + 0x81,
21100              byte2 + 0x30,
21101              byte3 + 0x81,
21102              byte4 + 0x30];
21103    };
21104  }
21105
21106  /** @param {{fatal: boolean}} options */
21107  encoders['gb18030'] = function(options) {
21108    return new GB18030Encoder(options);
21109  };
21110  /** @param {{fatal: boolean}} options */
21111  decoders['gb18030'] = function(options) {
21112    return new GB18030Decoder(options);
21113  };
21114
21115
21116  //
21117  // 12. Legacy multi-byte Chinese (traditional) encodings
21118  //
21119
21120  // 12.1 big5
21121
21122  // 12.1.1 big5 decoder
21123  /**
21124   * @constructor
21125   * @implements {Decoder}
21126   * @param {{fatal: boolean}} options
21127   */
21128  function Big5Decoder(options) {
21129    var fatal = options.fatal;
21130    // big5's decoder has an associated big5 lead (initially 0x00).
21131    var /** @type {number} */ big5_lead = 0x00;
21132
21133    /**
21134     * @param {Stream} stream The stream of bytes being decoded.
21135     * @param {number} bite The next byte read from the stream.
21136     * @return {?(number|!Array.<number>)} The next code point(s)
21137     *     decoded, or null if not enough data exists in the input
21138     *     stream to decode a complete code point.
21139     */
21140    this.handler = function(stream, bite) {
21141      // 1. If byte is end-of-stream and big5 lead is not 0x00, set
21142      // big5 lead to 0x00 and return error.
21143      if (bite === end_of_stream && big5_lead !== 0x00) {
21144        big5_lead = 0x00;
21145        return decoderError(fatal);
21146      }
21147
21148      // 2. If byte is end-of-stream and big5 lead is 0x00, return
21149      // finished.
21150      if (bite === end_of_stream && big5_lead === 0x00)
21151        return finished;
21152
21153      // 3. If big5 lead is not 0x00, let lead be big5 lead, let
21154      // pointer be null, set big5 lead to 0x00, and then run these
21155      // substeps:
21156      if (big5_lead !== 0x00) {
21157        var lead = big5_lead;
21158        var pointer = null;
21159        big5_lead = 0x00;
21160
21161        // 1. Let offset be 0x40 if byte is less than 0x7F and 0x62
21162        // otherwise.
21163        var offset = bite < 0x7F ? 0x40 : 0x62;
21164
21165        // 2. If byte is in the range 0x40 to 0x7E or 0xA1 to 0xFE,
21166        // set pointer to (lead − 0x81) × 157 + (byte − offset).
21167        if (inRange(bite, 0x40, 0x7E) || inRange(bite, 0xA1, 0xFE))
21168          pointer = (lead - 0x81) * 157 + (bite - offset);
21169
21170        // 3. If there is a row in the table below whose first column
21171        // is pointer, return the two code points listed in its second
21172        // column
21173        // Pointer | Code points
21174        // --------+--------------
21175        // 1133    | U+00CA U+0304
21176        // 1135    | U+00CA U+030C
21177        // 1164    | U+00EA U+0304
21178        // 1166    | U+00EA U+030C
21179        switch (pointer) {
21180          case 1133: return [0x00CA, 0x0304];
21181          case 1135: return [0x00CA, 0x030C];
21182          case 1164: return [0x00EA, 0x0304];
21183          case 1166: return [0x00EA, 0x030C];
21184        }
21185
21186        // 4. Let code point be null if pointer is null and the index
21187        // code point for pointer in index big5 otherwise.
21188        var code_point = (pointer === null) ? null :
21189            indexCodePointFor(pointer, index('big5'));
21190
21191        // 5. If code point is null and byte is an ASCII byte, prepend
21192        // byte to stream.
21193        if (code_point === null && isASCIIByte(bite))
21194          stream.prepend(bite);
21195
21196        // 6. If code point is null, return error.
21197        if (code_point === null)
21198          return decoderError(fatal);
21199
21200        // 7. Return a code point whose value is code point.
21201        return code_point;
21202      }
21203
21204      // 4. If byte is an ASCII byte, return a code point whose value
21205      // is byte.
21206      if (isASCIIByte(bite))
21207        return bite;
21208
21209      // 5. If byte is in the range 0x81 to 0xFE, set big5 lead to
21210      // byte and return continue.
21211      if (inRange(bite, 0x81, 0xFE)) {
21212        big5_lead = bite;
21213        return null;
21214      }
21215
21216      // 6. Return error.
21217      return decoderError(fatal);
21218    };
21219  }
21220
21221  // 12.1.2 big5 encoder
21222  /**
21223   * @constructor
21224   * @implements {Encoder}
21225   * @param {{fatal: boolean}} options
21226   */
21227  function Big5Encoder(options) {
21228    var fatal = options.fatal;
21229    /**
21230     * @param {Stream} stream Input stream.
21231     * @param {number} code_point Next code point read from the stream.
21232     * @return {(number|!Array.<number>)} Byte(s) to emit.
21233     */
21234    this.handler = function(stream, code_point) {
21235      // 1. If code point is end-of-stream, return finished.
21236      if (code_point === end_of_stream)
21237        return finished;
21238
21239      // 2. If code point is an ASCII code point, return a byte whose
21240      // value is code point.
21241      if (isASCIICodePoint(code_point))
21242        return code_point;
21243
21244      // 3. Let pointer be the index big5 pointer for code point.
21245      var pointer = indexBig5PointerFor(code_point);
21246
21247      // 4. If pointer is null, return error with code point.
21248      if (pointer === null)
21249        return encoderError(code_point);
21250
21251      // 5. Let lead be floor(pointer / 157) + 0x81.
21252      var lead = floor(pointer / 157) + 0x81;
21253
21254      // 6. If lead is less than 0xA1, return error with code point.
21255      if (lead < 0xA1)
21256        return encoderError(code_point);
21257
21258      // 7. Let trail be pointer % 157.
21259      var trail = pointer % 157;
21260
21261      // 8. Let offset be 0x40 if trail is less than 0x3F and 0x62
21262      // otherwise.
21263      var offset = trail < 0x3F ? 0x40 : 0x62;
21264
21265      // Return two bytes whose values are lead and trail + offset.
21266      return [lead, trail + offset];
21267    };
21268  }
21269
21270  /** @param {{fatal: boolean}} options */
21271  encoders['Big5'] = function(options) {
21272    return new Big5Encoder(options);
21273  };
21274  /** @param {{fatal: boolean}} options */
21275  decoders['Big5'] = function(options) {
21276    return new Big5Decoder(options);
21277  };
21278
21279
21280  //
21281  // 13. Legacy multi-byte Japanese encodings
21282  //
21283
21284  // 13.1 euc-jp
21285
21286  // 13.1.1 euc-jp decoder
21287  /**
21288   * @constructor
21289   * @implements {Decoder}
21290   * @param {{fatal: boolean}} options
21291   */
21292  function EUCJPDecoder(options) {
21293    var fatal = options.fatal;
21294
21295    // euc-jp's decoder has an associated euc-jp jis0212 flag
21296    // (initially unset) and euc-jp lead (initially 0x00).
21297    var /** @type {boolean} */ eucjp_jis0212_flag = false,
21298        /** @type {number} */ eucjp_lead = 0x00;
21299
21300    /**
21301     * @param {Stream} stream The stream of bytes being decoded.
21302     * @param {number} bite The next byte read from the stream.
21303     * @return {?(number|!Array.<number>)} The next code point(s)
21304     *     decoded, or null if not enough data exists in the input
21305     *     stream to decode a complete code point.
21306     */
21307    this.handler = function(stream, bite) {
21308      // 1. If byte is end-of-stream and euc-jp lead is not 0x00, set
21309      // euc-jp lead to 0x00, and return error.
21310      if (bite === end_of_stream && eucjp_lead !== 0x00) {
21311        eucjp_lead = 0x00;
21312        return decoderError(fatal);
21313      }
21314
21315      // 2. If byte is end-of-stream and euc-jp lead is 0x00, return
21316      // finished.
21317      if (bite === end_of_stream && eucjp_lead === 0x00)
21318        return finished;
21319
21320      // 3. If euc-jp lead is 0x8E and byte is in the range 0xA1 to
21321      // 0xDF, set euc-jp lead to 0x00 and return a code point whose
21322      // value is 0xFF61 + byte − 0xA1.
21323      if (eucjp_lead === 0x8E && inRange(bite, 0xA1, 0xDF)) {
21324        eucjp_lead = 0x00;
21325        return 0xFF61 + bite - 0xA1;
21326      }
21327
21328      // 4. If euc-jp lead is 0x8F and byte is in the range 0xA1 to
21329      // 0xFE, set the euc-jp jis0212 flag, set euc-jp lead to byte,
21330      // and return continue.
21331      if (eucjp_lead === 0x8F && inRange(bite, 0xA1, 0xFE)) {
21332        eucjp_jis0212_flag = true;
21333        eucjp_lead = bite;
21334        return null;
21335      }
21336
21337      // 5. If euc-jp lead is not 0x00, let lead be euc-jp lead, set
21338      // euc-jp lead to 0x00, and run these substeps:
21339      if (eucjp_lead !== 0x00) {
21340        var lead = eucjp_lead;
21341        eucjp_lead = 0x00;
21342
21343        // 1. Let code point be null.
21344        var code_point = null;
21345
21346        // 2. If lead and byte are both in the range 0xA1 to 0xFE, set
21347        // code point to the index code point for (lead − 0xA1) × 94 +
21348        // byte − 0xA1 in index jis0208 if the euc-jp jis0212 flag is
21349        // unset and in index jis0212 otherwise.
21350        if (inRange(lead, 0xA1, 0xFE) && inRange(bite, 0xA1, 0xFE)) {
21351          code_point = indexCodePointFor(
21352            (lead - 0xA1) * 94 + (bite - 0xA1),
21353            index(!eucjp_jis0212_flag ? 'jis0208' : 'jis0212'));
21354        }
21355
21356        // 3. Unset the euc-jp jis0212 flag.
21357        eucjp_jis0212_flag = false;
21358
21359        // 4. If byte is not in the range 0xA1 to 0xFE, prepend byte
21360        // to stream.
21361        if (!inRange(bite, 0xA1, 0xFE))
21362          stream.prepend(bite);
21363
21364        // 5. If code point is null, return error.
21365        if (code_point === null)
21366          return decoderError(fatal);
21367
21368        // 6. Return a code point whose value is code point.
21369        return code_point;
21370      }
21371
21372      // 6. If byte is an ASCII byte, return a code point whose value
21373      // is byte.
21374      if (isASCIIByte(bite))
21375        return bite;
21376
21377      // 7. If byte is 0x8E, 0x8F, or in the range 0xA1 to 0xFE, set
21378      // euc-jp lead to byte and return continue.
21379      if (bite === 0x8E || bite === 0x8F || inRange(bite, 0xA1, 0xFE)) {
21380        eucjp_lead = bite;
21381        return null;
21382      }
21383
21384      // 8. Return error.
21385      return decoderError(fatal);
21386    };
21387  }
21388
21389  // 13.1.2 euc-jp encoder
21390  /**
21391   * @constructor
21392   * @implements {Encoder}
21393   * @param {{fatal: boolean}} options
21394   */
21395  function EUCJPEncoder(options) {
21396    var fatal = options.fatal;
21397    /**
21398     * @param {Stream} stream Input stream.
21399     * @param {number} code_point Next code point read from the stream.
21400     * @return {(number|!Array.<number>)} Byte(s) to emit.
21401     */
21402    this.handler = function(stream, code_point) {
21403      // 1. If code point is end-of-stream, return finished.
21404      if (code_point === end_of_stream)
21405        return finished;
21406
21407      // 2. If code point is an ASCII code point, return a byte whose
21408      // value is code point.
21409      if (isASCIICodePoint(code_point))
21410        return code_point;
21411
21412      // 3. If code point is U+00A5, return byte 0x5C.
21413      if (code_point === 0x00A5)
21414        return 0x5C;
21415
21416      // 4. If code point is U+203E, return byte 0x7E.
21417      if (code_point === 0x203E)
21418        return 0x7E;
21419
21420      // 5. If code point is in the range U+FF61 to U+FF9F, return two
21421      // bytes whose values are 0x8E and code point − 0xFF61 + 0xA1.
21422      if (inRange(code_point, 0xFF61, 0xFF9F))
21423        return [0x8E, code_point - 0xFF61 + 0xA1];
21424
21425      // 6. If code point is U+2212, set it to U+FF0D.
21426      if (code_point === 0x2212)
21427        code_point = 0xFF0D;
21428
21429      // 7. Let pointer be the index pointer for code point in index
21430      // jis0208.
21431      var pointer = indexPointerFor(code_point, index('jis0208'));
21432
21433      // 8. If pointer is null, return error with code point.
21434      if (pointer === null)
21435        return encoderError(code_point);
21436
21437      // 9. Let lead be floor(pointer / 94) + 0xA1.
21438      var lead = floor(pointer / 94) + 0xA1;
21439
21440      // 10. Let trail be pointer % 94 + 0xA1.
21441      var trail = pointer % 94 + 0xA1;
21442
21443      // 11. Return two bytes whose values are lead and trail.
21444      return [lead, trail];
21445    };
21446  }
21447
21448  /** @param {{fatal: boolean}} options */
21449  encoders['EUC-JP'] = function(options) {
21450    return new EUCJPEncoder(options);
21451  };
21452  /** @param {{fatal: boolean}} options */
21453  decoders['EUC-JP'] = function(options) {
21454    return new EUCJPDecoder(options);
21455  };
21456
21457  // 13.2 iso-2022-jp
21458
21459  // 13.2.1 iso-2022-jp decoder
21460  /**
21461   * @constructor
21462   * @implements {Decoder}
21463   * @param {{fatal: boolean}} options
21464   */
21465  function ISO2022JPDecoder(options) {
21466    var fatal = options.fatal;
21467    /** @enum */
21468    var states = {
21469      ASCII: 0,
21470      Roman: 1,
21471      Katakana: 2,
21472      LeadByte: 3,
21473      TrailByte: 4,
21474      EscapeStart: 5,
21475      Escape: 6
21476    };
21477    // iso-2022-jp's decoder has an associated iso-2022-jp decoder
21478    // state (initially ASCII), iso-2022-jp decoder output state
21479    // (initially ASCII), iso-2022-jp lead (initially 0x00), and
21480    // iso-2022-jp output flag (initially unset).
21481    var /** @type {number} */ iso2022jp_decoder_state = states.ASCII,
21482        /** @type {number} */ iso2022jp_decoder_output_state = states.ASCII,
21483        /** @type {number} */ iso2022jp_lead = 0x00,
21484        /** @type {boolean} */ iso2022jp_output_flag = false;
21485    /**
21486     * @param {Stream} stream The stream of bytes being decoded.
21487     * @param {number} bite The next byte read from the stream.
21488     * @return {?(number|!Array.<number>)} The next code point(s)
21489     *     decoded, or null if not enough data exists in the input
21490     *     stream to decode a complete code point.
21491     */
21492    this.handler = function(stream, bite) {
21493      // switching on iso-2022-jp decoder state:
21494      switch (iso2022jp_decoder_state) {
21495      default:
21496      case states.ASCII:
21497        // ASCII
21498        // Based on byte:
21499
21500        // 0x1B
21501        if (bite === 0x1B) {
21502          // Set iso-2022-jp decoder state to escape start and return
21503          // continue.
21504          iso2022jp_decoder_state = states.EscapeStart;
21505          return null;
21506        }
21507
21508        // 0x00 to 0x7F, excluding 0x0E, 0x0F, and 0x1B
21509        if (inRange(bite, 0x00, 0x7F) && bite !== 0x0E
21510            && bite !== 0x0F && bite !== 0x1B) {
21511          // Unset the iso-2022-jp output flag and return a code point
21512          // whose value is byte.
21513          iso2022jp_output_flag = false;
21514          return bite;
21515        }
21516
21517        // end-of-stream
21518        if (bite === end_of_stream) {
21519          // Return finished.
21520          return finished;
21521        }
21522
21523        // Otherwise
21524        // Unset the iso-2022-jp output flag and return error.
21525        iso2022jp_output_flag = false;
21526        return decoderError(fatal);
21527
21528      case states.Roman:
21529        // Roman
21530        // Based on byte:
21531
21532        // 0x1B
21533        if (bite === 0x1B) {
21534          // Set iso-2022-jp decoder state to escape start and return
21535          // continue.
21536          iso2022jp_decoder_state = states.EscapeStart;
21537          return null;
21538        }
21539
21540        // 0x5C
21541        if (bite === 0x5C) {
21542          // Unset the iso-2022-jp output flag and return code point
21543          // U+00A5.
21544          iso2022jp_output_flag = false;
21545          return 0x00A5;
21546        }
21547
21548        // 0x7E
21549        if (bite === 0x7E) {
21550          // Unset the iso-2022-jp output flag and return code point
21551          // U+203E.
21552          iso2022jp_output_flag = false;
21553          return 0x203E;
21554        }
21555
21556        // 0x00 to 0x7F, excluding 0x0E, 0x0F, 0x1B, 0x5C, and 0x7E
21557        if (inRange(bite, 0x00, 0x7F) && bite !== 0x0E && bite !== 0x0F
21558            && bite !== 0x1B && bite !== 0x5C && bite !== 0x7E) {
21559          // Unset the iso-2022-jp output flag and return a code point
21560          // whose value is byte.
21561          iso2022jp_output_flag = false;
21562          return bite;
21563        }
21564
21565        // end-of-stream
21566        if (bite === end_of_stream) {
21567          // Return finished.
21568          return finished;
21569        }
21570
21571        // Otherwise
21572        // Unset the iso-2022-jp output flag and return error.
21573        iso2022jp_output_flag = false;
21574        return decoderError(fatal);
21575
21576      case states.Katakana:
21577        // Katakana
21578        // Based on byte:
21579
21580        // 0x1B
21581        if (bite === 0x1B) {
21582          // Set iso-2022-jp decoder state to escape start and return
21583          // continue.
21584          iso2022jp_decoder_state = states.EscapeStart;
21585          return null;
21586        }
21587
21588        // 0x21 to 0x5F
21589        if (inRange(bite, 0x21, 0x5F)) {
21590          // Unset the iso-2022-jp output flag and return a code point
21591          // whose value is 0xFF61 + byte − 0x21.
21592          iso2022jp_output_flag = false;
21593          return 0xFF61 + bite - 0x21;
21594        }
21595
21596        // end-of-stream
21597        if (bite === end_of_stream) {
21598          // Return finished.
21599          return finished;
21600        }
21601
21602        // Otherwise
21603        // Unset the iso-2022-jp output flag and return error.
21604        iso2022jp_output_flag = false;
21605        return decoderError(fatal);
21606
21607      case states.LeadByte:
21608        // Lead byte
21609        // Based on byte:
21610
21611        // 0x1B
21612        if (bite === 0x1B) {
21613          // Set iso-2022-jp decoder state to escape start and return
21614          // continue.
21615          iso2022jp_decoder_state = states.EscapeStart;
21616          return null;
21617        }
21618
21619        // 0x21 to 0x7E
21620        if (inRange(bite, 0x21, 0x7E)) {
21621          // Unset the iso-2022-jp output flag, set iso-2022-jp lead
21622          // to byte, iso-2022-jp decoder state to trail byte, and
21623          // return continue.
21624          iso2022jp_output_flag = false;
21625          iso2022jp_lead = bite;
21626          iso2022jp_decoder_state = states.TrailByte;
21627          return null;
21628        }
21629
21630        // end-of-stream
21631        if (bite === end_of_stream) {
21632          // Return finished.
21633          return finished;
21634        }
21635
21636        // Otherwise
21637        // Unset the iso-2022-jp output flag and return error.
21638        iso2022jp_output_flag = false;
21639        return decoderError(fatal);
21640
21641      case states.TrailByte:
21642        // Trail byte
21643        // Based on byte:
21644
21645        // 0x1B
21646        if (bite === 0x1B) {
21647          // Set iso-2022-jp decoder state to escape start and return
21648          // continue.
21649          iso2022jp_decoder_state = states.EscapeStart;
21650          return decoderError(fatal);
21651        }
21652
21653        // 0x21 to 0x7E
21654        if (inRange(bite, 0x21, 0x7E)) {
21655          // 1. Set the iso-2022-jp decoder state to lead byte.
21656          iso2022jp_decoder_state = states.LeadByte;
21657
21658          // 2. Let pointer be (iso-2022-jp lead − 0x21) × 94 + byte − 0x21.
21659          var pointer = (iso2022jp_lead - 0x21) * 94 + bite - 0x21;
21660
21661          // 3. Let code point be the index code point for pointer in
21662          // index jis0208.
21663          var code_point = indexCodePointFor(pointer, index('jis0208'));
21664
21665          // 4. If code point is null, return error.
21666          if (code_point === null)
21667            return decoderError(fatal);
21668
21669          // 5. Return a code point whose value is code point.
21670          return code_point;
21671        }
21672
21673        // end-of-stream
21674        if (bite === end_of_stream) {
21675          // Set the iso-2022-jp decoder state to lead byte, prepend
21676          // byte to stream, and return error.
21677          iso2022jp_decoder_state = states.LeadByte;
21678          stream.prepend(bite);
21679          return decoderError(fatal);
21680        }
21681
21682        // Otherwise
21683        // Set iso-2022-jp decoder state to lead byte and return
21684        // error.
21685        iso2022jp_decoder_state = states.LeadByte;
21686        return decoderError(fatal);
21687
21688      case states.EscapeStart:
21689        // Escape start
21690
21691        // 1. If byte is either 0x24 or 0x28, set iso-2022-jp lead to
21692        // byte, iso-2022-jp decoder state to escape, and return
21693        // continue.
21694        if (bite === 0x24 || bite === 0x28) {
21695          iso2022jp_lead = bite;
21696          iso2022jp_decoder_state = states.Escape;
21697          return null;
21698        }
21699
21700        // 2. Prepend byte to stream.
21701        stream.prepend(bite);
21702
21703        // 3. Unset the iso-2022-jp output flag, set iso-2022-jp
21704        // decoder state to iso-2022-jp decoder output state, and
21705        // return error.
21706        iso2022jp_output_flag = false;
21707        iso2022jp_decoder_state = iso2022jp_decoder_output_state;
21708        return decoderError(fatal);
21709
21710      case states.Escape:
21711        // Escape
21712
21713        // 1. Let lead be iso-2022-jp lead and set iso-2022-jp lead to
21714        // 0x00.
21715        var lead = iso2022jp_lead;
21716        iso2022jp_lead = 0x00;
21717
21718        // 2. Let state be null.
21719        var state = null;
21720
21721        // 3. If lead is 0x28 and byte is 0x42, set state to ASCII.
21722        if (lead === 0x28 && bite === 0x42)
21723          state = states.ASCII;
21724
21725        // 4. If lead is 0x28 and byte is 0x4A, set state to Roman.
21726        if (lead === 0x28 && bite === 0x4A)
21727          state = states.Roman;
21728
21729        // 5. If lead is 0x28 and byte is 0x49, set state to Katakana.
21730        if (lead === 0x28 && bite === 0x49)
21731          state = states.Katakana;
21732
21733        // 6. If lead is 0x24 and byte is either 0x40 or 0x42, set
21734        // state to lead byte.
21735        if (lead === 0x24 && (bite === 0x40 || bite === 0x42))
21736          state = states.LeadByte;
21737
21738        // 7. If state is non-null, run these substeps:
21739        if (state !== null) {
21740          // 1. Set iso-2022-jp decoder state and iso-2022-jp decoder
21741          // output state to states.
21742          iso2022jp_decoder_state = iso2022jp_decoder_state = state;
21743
21744          // 2. Let output flag be the iso-2022-jp output flag.
21745          var output_flag = iso2022jp_output_flag;
21746
21747          // 3. Set the iso-2022-jp output flag.
21748          iso2022jp_output_flag = true;
21749
21750          // 4. Return continue, if output flag is unset, and error
21751          // otherwise.
21752          return !output_flag ? null : decoderError(fatal);
21753        }
21754
21755        // 8. Prepend lead and byte to stream.
21756        stream.prepend([lead, bite]);
21757
21758        // 9. Unset the iso-2022-jp output flag, set iso-2022-jp
21759        // decoder state to iso-2022-jp decoder output state and
21760        // return error.
21761        iso2022jp_output_flag = false;
21762        iso2022jp_decoder_state = iso2022jp_decoder_output_state;
21763        return decoderError(fatal);
21764      }
21765    };
21766  }
21767
21768  // 13.2.2 iso-2022-jp encoder
21769  /**
21770   * @constructor
21771   * @implements {Encoder}
21772   * @param {{fatal: boolean}} options
21773   */
21774  function ISO2022JPEncoder(options) {
21775    var fatal = options.fatal;
21776    // iso-2022-jp's encoder has an associated iso-2022-jp encoder
21777    // state which is one of ASCII, Roman, and jis0208 (initially
21778    // ASCII).
21779    /** @enum */
21780    var states = {
21781      ASCII: 0,
21782      Roman: 1,
21783      jis0208: 2
21784    };
21785    var /** @type {number} */ iso2022jp_state = states.ASCII;
21786    /**
21787     * @param {Stream} stream Input stream.
21788     * @param {number} code_point Next code point read from the stream.
21789     * @return {(number|!Array.<number>)} Byte(s) to emit.
21790     */
21791    this.handler = function(stream, code_point) {
21792      // 1. If code point is end-of-stream and iso-2022-jp encoder
21793      // state is not ASCII, prepend code point to stream, set
21794      // iso-2022-jp encoder state to ASCII, and return three bytes
21795      // 0x1B 0x28 0x42.
21796      if (code_point === end_of_stream &&
21797          iso2022jp_state !== states.ASCII) {
21798        stream.prepend(code_point);
21799        iso2022jp_state = states.ASCII;
21800        return [0x1B, 0x28, 0x42];
21801      }
21802
21803      // 2. If code point is end-of-stream and iso-2022-jp encoder
21804      // state is ASCII, return finished.
21805      if (code_point === end_of_stream && iso2022jp_state === states.ASCII)
21806        return finished;
21807
21808      // 3. If ISO-2022-JP encoder state is ASCII or Roman, and code
21809      // point is U+000E, U+000F, or U+001B, return error with U+FFFD.
21810      if ((iso2022jp_state === states.ASCII ||
21811           iso2022jp_state === states.Roman) &&
21812          (code_point === 0x000E || code_point === 0x000F ||
21813           code_point === 0x001B)) {
21814        return encoderError(0xFFFD);
21815      }
21816
21817      // 4. If iso-2022-jp encoder state is ASCII and code point is an
21818      // ASCII code point, return a byte whose value is code point.
21819      if (iso2022jp_state === states.ASCII &&
21820          isASCIICodePoint(code_point))
21821        return code_point;
21822
21823      // 5. If iso-2022-jp encoder state is Roman and code point is an
21824      // ASCII code point, excluding U+005C and U+007E, or is U+00A5
21825      // or U+203E, run these substeps:
21826      if (iso2022jp_state === states.Roman &&
21827          ((isASCIICodePoint(code_point) &&
21828           code_point !== 0x005C && code_point !== 0x007E) ||
21829          (code_point == 0x00A5 || code_point == 0x203E))) {
21830
21831        // 1. If code point is an ASCII code point, return a byte
21832        // whose value is code point.
21833        if (isASCIICodePoint(code_point))
21834          return code_point;
21835
21836        // 2. If code point is U+00A5, return byte 0x5C.
21837        if (code_point === 0x00A5)
21838          return 0x5C;
21839
21840        // 3. If code point is U+203E, return byte 0x7E.
21841        if (code_point === 0x203E)
21842          return 0x7E;
21843      }
21844
21845      // 6. If code point is an ASCII code point, and iso-2022-jp
21846      // encoder state is not ASCII, prepend code point to stream, set
21847      // iso-2022-jp encoder state to ASCII, and return three bytes
21848      // 0x1B 0x28 0x42.
21849      if (isASCIICodePoint(code_point) &&
21850          iso2022jp_state !== states.ASCII) {
21851        stream.prepend(code_point);
21852        iso2022jp_state = states.ASCII;
21853        return [0x1B, 0x28, 0x42];
21854      }
21855
21856      // 7. If code point is either U+00A5 or U+203E, and iso-2022-jp
21857      // encoder state is not Roman, prepend code point to stream, set
21858      // iso-2022-jp encoder state to Roman, and return three bytes
21859      // 0x1B 0x28 0x4A.
21860      if ((code_point === 0x00A5 || code_point === 0x203E) &&
21861          iso2022jp_state !== states.Roman) {
21862        stream.prepend(code_point);
21863        iso2022jp_state = states.Roman;
21864        return [0x1B, 0x28, 0x4A];
21865      }
21866
21867      // 8. If code point is U+2212, set it to U+FF0D.
21868      if (code_point === 0x2212)
21869        code_point = 0xFF0D;
21870
21871      // 9. Let pointer be the index pointer for code point in index
21872      // jis0208.
21873      var pointer = indexPointerFor(code_point, index('jis0208'));
21874
21875      // 10. If pointer is null, return error with code point.
21876      if (pointer === null)
21877        return encoderError(code_point);
21878
21879      // 11. If iso-2022-jp encoder state is not jis0208, prepend code
21880      // point to stream, set iso-2022-jp encoder state to jis0208,
21881      // and return three bytes 0x1B 0x24 0x42.
21882      if (iso2022jp_state !== states.jis0208) {
21883        stream.prepend(code_point);
21884        iso2022jp_state = states.jis0208;
21885        return [0x1B, 0x24, 0x42];
21886      }
21887
21888      // 12. Let lead be floor(pointer / 94) + 0x21.
21889      var lead = floor(pointer / 94) + 0x21;
21890
21891      // 13. Let trail be pointer % 94 + 0x21.
21892      var trail = pointer % 94 + 0x21;
21893
21894      // 14. Return two bytes whose values are lead and trail.
21895      return [lead, trail];
21896    };
21897  }
21898
21899  /** @param {{fatal: boolean}} options */
21900  encoders['ISO-2022-JP'] = function(options) {
21901    return new ISO2022JPEncoder(options);
21902  };
21903  /** @param {{fatal: boolean}} options */
21904  decoders['ISO-2022-JP'] = function(options) {
21905    return new ISO2022JPDecoder(options);
21906  };
21907
21908  // 13.3 shift_jis
21909
21910  // 13.3.1 shift_jis decoder
21911  /**
21912   * @constructor
21913   * @implements {Decoder}
21914   * @param {{fatal: boolean}} options
21915   */
21916  function ShiftJISDecoder(options) {
21917    var fatal = options.fatal;
21918    // shift_jis's decoder has an associated shift_jis lead (initially
21919    // 0x00).
21920    var /** @type {number} */ shiftjis_lead = 0x00;
21921    /**
21922     * @param {Stream} stream The stream of bytes being decoded.
21923     * @param {number} bite The next byte read from the stream.
21924     * @return {?(number|!Array.<number>)} The next code point(s)
21925     *     decoded, or null if not enough data exists in the input
21926     *     stream to decode a complete code point.
21927     */
21928    this.handler = function(stream, bite) {
21929      // 1. If byte is end-of-stream and shift_jis lead is not 0x00,
21930      // set shift_jis lead to 0x00 and return error.
21931      if (bite === end_of_stream && shiftjis_lead !== 0x00) {
21932        shiftjis_lead = 0x00;
21933        return decoderError(fatal);
21934      }
21935
21936      // 2. If byte is end-of-stream and shift_jis lead is 0x00,
21937      // return finished.
21938      if (bite === end_of_stream && shiftjis_lead === 0x00)
21939        return finished;
21940
21941      // 3. If shift_jis lead is not 0x00, let lead be shift_jis lead,
21942      // let pointer be null, set shift_jis lead to 0x00, and then run
21943      // these substeps:
21944      if (shiftjis_lead !== 0x00) {
21945        var lead = shiftjis_lead;
21946        var pointer = null;
21947        shiftjis_lead = 0x00;
21948
21949        // 1. Let offset be 0x40, if byte is less than 0x7F, and 0x41
21950        // otherwise.
21951        var offset = (bite < 0x7F) ? 0x40 : 0x41;
21952
21953        // 2. Let lead offset be 0x81, if lead is less than 0xA0, and
21954        // 0xC1 otherwise.
21955        var lead_offset = (lead < 0xA0) ? 0x81 : 0xC1;
21956
21957        // 3. If byte is in the range 0x40 to 0x7E or 0x80 to 0xFC,
21958        // set pointer to (lead − lead offset) × 188 + byte − offset.
21959        if (inRange(bite, 0x40, 0x7E) || inRange(bite, 0x80, 0xFC))
21960          pointer = (lead - lead_offset) * 188 + bite - offset;
21961
21962        // 4. Let code point be null, if pointer is null, and the
21963        // index code point for pointer in index jis0208 otherwise.
21964        var code_point = (pointer === null) ? null :
21965              indexCodePointFor(pointer, index('jis0208'));
21966
21967        // 5. If code point is null and pointer is in the range 8836
21968        // to 10528, return a code point whose value is 0xE000 +
21969        // pointer − 8836.
21970        if (code_point === null && pointer !== null &&
21971            inRange(pointer, 8836, 10528))
21972          return 0xE000 + pointer - 8836;
21973
21974        // 6. If code point is null and byte is an ASCII byte, prepend
21975        // byte to stream.
21976        if (code_point === null && isASCIIByte(bite))
21977          stream.prepend(bite);
21978
21979        // 7. If code point is null, return error.
21980        if (code_point === null)
21981          return decoderError(fatal);
21982
21983        // 8. Return a code point whose value is code point.
21984        return code_point;
21985      }
21986
21987      // 4. If byte is an ASCII byte or 0x80, return a code point
21988      // whose value is byte.
21989      if (isASCIIByte(bite) || bite === 0x80)
21990        return bite;
21991
21992      // 5. If byte is in the range 0xA1 to 0xDF, return a code point
21993      // whose value is 0xFF61 + byte − 0xA1.
21994      if (inRange(bite, 0xA1, 0xDF))
21995        return 0xFF61 + bite - 0xA1;
21996
21997      // 6. If byte is in the range 0x81 to 0x9F or 0xE0 to 0xFC, set
21998      // shift_jis lead to byte and return continue.
21999      if (inRange(bite, 0x81, 0x9F) || inRange(bite, 0xE0, 0xFC)) {
22000        shiftjis_lead = bite;
22001        return null;
22002      }
22003
22004      // 7. Return error.
22005      return decoderError(fatal);
22006    };
22007  }
22008
22009  // 13.3.2 shift_jis encoder
22010  /**
22011   * @constructor
22012   * @implements {Encoder}
22013   * @param {{fatal: boolean}} options
22014   */
22015  function ShiftJISEncoder(options) {
22016    var fatal = options.fatal;
22017    /**
22018     * @param {Stream} stream Input stream.
22019     * @param {number} code_point Next code point read from the stream.
22020     * @return {(number|!Array.<number>)} Byte(s) to emit.
22021     */
22022    this.handler = function(stream, code_point) {
22023      // 1. If code point is end-of-stream, return finished.
22024      if (code_point === end_of_stream)
22025        return finished;
22026
22027      // 2. If code point is an ASCII code point or U+0080, return a
22028      // byte whose value is code point.
22029      if (isASCIICodePoint(code_point) || code_point === 0x0080)
22030        return code_point;
22031
22032      // 3. If code point is U+00A5, return byte 0x5C.
22033      if (code_point === 0x00A5)
22034        return 0x5C;
22035
22036      // 4. If code point is U+203E, return byte 0x7E.
22037      if (code_point === 0x203E)
22038        return 0x7E;
22039
22040      // 5. If code point is in the range U+FF61 to U+FF9F, return a
22041      // byte whose value is code point − 0xFF61 + 0xA1.
22042      if (inRange(code_point, 0xFF61, 0xFF9F))
22043        return code_point - 0xFF61 + 0xA1;
22044
22045      // 6. If code point is U+2212, set it to U+FF0D.
22046      if (code_point === 0x2212)
22047        code_point = 0xFF0D;
22048
22049      // 7. Let pointer be the index shift_jis pointer for code point.
22050      var pointer = indexShiftJISPointerFor(code_point);
22051
22052      // 8. If pointer is null, return error with code point.
22053      if (pointer === null)
22054        return encoderError(code_point);
22055
22056      // 9. Let lead be floor(pointer / 188).
22057      var lead = floor(pointer / 188);
22058
22059      // 10. Let lead offset be 0x81, if lead is less than 0x1F, and
22060      // 0xC1 otherwise.
22061      var lead_offset = (lead < 0x1F) ? 0x81 : 0xC1;
22062
22063      // 11. Let trail be pointer % 188.
22064      var trail = pointer % 188;
22065
22066      // 12. Let offset be 0x40, if trail is less than 0x3F, and 0x41
22067      // otherwise.
22068      var offset = (trail < 0x3F) ? 0x40 : 0x41;
22069
22070      // 13. Return two bytes whose values are lead + lead offset and
22071      // trail + offset.
22072      return [lead + lead_offset, trail + offset];
22073    };
22074  }
22075
22076  /** @param {{fatal: boolean}} options */
22077  encoders['Shift_JIS'] = function(options) {
22078    return new ShiftJISEncoder(options);
22079  };
22080  /** @param {{fatal: boolean}} options */
22081  decoders['Shift_JIS'] = function(options) {
22082    return new ShiftJISDecoder(options);
22083  };
22084
22085  //
22086  // 14. Legacy multi-byte Korean encodings
22087  //
22088
22089  // 14.1 euc-kr
22090
22091  // 14.1.1 euc-kr decoder
22092  /**
22093   * @constructor
22094   * @implements {Decoder}
22095   * @param {{fatal: boolean}} options
22096   */
22097  function EUCKRDecoder(options) {
22098    var fatal = options.fatal;
22099
22100    // euc-kr's decoder has an associated euc-kr lead (initially 0x00).
22101    var /** @type {number} */ euckr_lead = 0x00;
22102    /**
22103     * @param {Stream} stream The stream of bytes being decoded.
22104     * @param {number} bite The next byte read from the stream.
22105     * @return {?(number|!Array.<number>)} The next code point(s)
22106     *     decoded, or null if not enough data exists in the input
22107     *     stream to decode a complete code point.
22108     */
22109    this.handler = function(stream, bite) {
22110      // 1. If byte is end-of-stream and euc-kr lead is not 0x00, set
22111      // euc-kr lead to 0x00 and return error.
22112      if (bite === end_of_stream && euckr_lead !== 0) {
22113        euckr_lead = 0x00;
22114        return decoderError(fatal);
22115      }
22116
22117      // 2. If byte is end-of-stream and euc-kr lead is 0x00, return
22118      // finished.
22119      if (bite === end_of_stream && euckr_lead === 0)
22120        return finished;
22121
22122      // 3. If euc-kr lead is not 0x00, let lead be euc-kr lead, let
22123      // pointer be null, set euc-kr lead to 0x00, and then run these
22124      // substeps:
22125      if (euckr_lead !== 0x00) {
22126        var lead = euckr_lead;
22127        var pointer = null;
22128        euckr_lead = 0x00;
22129
22130        // 1. If byte is in the range 0x41 to 0xFE, set pointer to
22131        // (lead − 0x81) × 190 + (byte − 0x41).
22132        if (inRange(bite, 0x41, 0xFE))
22133          pointer = (lead - 0x81) * 190 + (bite - 0x41);
22134
22135        // 2. Let code point be null, if pointer is null, and the
22136        // index code point for pointer in index euc-kr otherwise.
22137        var code_point = (pointer === null)
22138              ? null : indexCodePointFor(pointer, index('euc-kr'));
22139
22140        // 3. If code point is null and byte is an ASCII byte, prepend
22141        // byte to stream.
22142        if (pointer === null && isASCIIByte(bite))
22143          stream.prepend(bite);
22144
22145        // 4. If code point is null, return error.
22146        if (code_point === null)
22147          return decoderError(fatal);
22148
22149        // 5. Return a code point whose value is code point.
22150        return code_point;
22151      }
22152
22153      // 4. If byte is an ASCII byte, return a code point whose value
22154      // is byte.
22155      if (isASCIIByte(bite))
22156        return bite;
22157
22158      // 5. If byte is in the range 0x81 to 0xFE, set euc-kr lead to
22159      // byte and return continue.
22160      if (inRange(bite, 0x81, 0xFE)) {
22161        euckr_lead = bite;
22162        return null;
22163      }
22164
22165      // 6. Return error.
22166      return decoderError(fatal);
22167    };
22168  }
22169
22170  // 14.1.2 euc-kr encoder
22171  /**
22172   * @constructor
22173   * @implements {Encoder}
22174   * @param {{fatal: boolean}} options
22175   */
22176  function EUCKREncoder(options) {
22177    var fatal = options.fatal;
22178    /**
22179     * @param {Stream} stream Input stream.
22180     * @param {number} code_point Next code point read from the stream.
22181     * @return {(number|!Array.<number>)} Byte(s) to emit.
22182     */
22183    this.handler = function(stream, code_point) {
22184      // 1. If code point is end-of-stream, return finished.
22185      if (code_point === end_of_stream)
22186        return finished;
22187
22188      // 2. If code point is an ASCII code point, return a byte whose
22189      // value is code point.
22190      if (isASCIICodePoint(code_point))
22191        return code_point;
22192
22193      // 3. Let pointer be the index pointer for code point in index
22194      // euc-kr.
22195      var pointer = indexPointerFor(code_point, index('euc-kr'));
22196
22197      // 4. If pointer is null, return error with code point.
22198      if (pointer === null)
22199        return encoderError(code_point);
22200
22201      // 5. Let lead be floor(pointer / 190) + 0x81.
22202      var lead = floor(pointer / 190) + 0x81;
22203
22204      // 6. Let trail be pointer % 190 + 0x41.
22205      var trail = (pointer % 190) + 0x41;
22206
22207      // 7. Return two bytes whose values are lead and trail.
22208      return [lead, trail];
22209    };
22210  }
22211
22212  /** @param {{fatal: boolean}} options */
22213  encoders['EUC-KR'] = function(options) {
22214    return new EUCKREncoder(options);
22215  };
22216  /** @param {{fatal: boolean}} options */
22217  decoders['EUC-KR'] = function(options) {
22218    return new EUCKRDecoder(options);
22219  };
22220
22221
22222  //
22223  // 15. Legacy miscellaneous encodings
22224  //
22225
22226  // 15.1 replacement
22227
22228  // Not needed - API throws RangeError
22229
22230  // 15.2 Common infrastructure for utf-16be and utf-16le
22231
22232  /**
22233   * @param {number} code_unit
22234   * @param {boolean} utf16be
22235   * @return {!Array.<number>} bytes
22236   */
22237  function convertCodeUnitToBytes(code_unit, utf16be) {
22238    // 1. Let byte1 be code unit >> 8.
22239    var byte1 = code_unit >> 8;
22240
22241    // 2. Let byte2 be code unit & 0x00FF.
22242    var byte2 = code_unit & 0x00FF;
22243
22244    // 3. Then return the bytes in order:
22245        // utf-16be flag is set: byte1, then byte2.
22246    if (utf16be)
22247      return [byte1, byte2];
22248    // utf-16be flag is unset: byte2, then byte1.
22249    return [byte2, byte1];
22250  }
22251
22252  // 15.2.1 shared utf-16 decoder
22253  /**
22254   * @constructor
22255   * @implements {Decoder}
22256   * @param {boolean} utf16_be True if big-endian, false if little-endian.
22257   * @param {{fatal: boolean}} options
22258   */
22259  function UTF16Decoder(utf16_be, options) {
22260    var fatal = options.fatal;
22261    var /** @type {?number} */ utf16_lead_byte = null,
22262        /** @type {?number} */ utf16_lead_surrogate = null;
22263    /**
22264     * @param {Stream} stream The stream of bytes being decoded.
22265     * @param {number} bite The next byte read from the stream.
22266     * @return {?(number|!Array.<number>)} The next code point(s)
22267     *     decoded, or null if not enough data exists in the input
22268     *     stream to decode a complete code point.
22269     */
22270    this.handler = function(stream, bite) {
22271      // 1. If byte is end-of-stream and either utf-16 lead byte or
22272      // utf-16 lead surrogate is not null, set utf-16 lead byte and
22273      // utf-16 lead surrogate to null, and return error.
22274      if (bite === end_of_stream && (utf16_lead_byte !== null ||
22275                                utf16_lead_surrogate !== null)) {
22276        return decoderError(fatal);
22277      }
22278
22279      // 2. If byte is end-of-stream and utf-16 lead byte and utf-16
22280      // lead surrogate are null, return finished.
22281      if (bite === end_of_stream && utf16_lead_byte === null &&
22282          utf16_lead_surrogate === null) {
22283        return finished;
22284      }
22285
22286      // 3. If utf-16 lead byte is null, set utf-16 lead byte to byte
22287      // and return continue.
22288      if (utf16_lead_byte === null) {
22289        utf16_lead_byte = bite;
22290        return null;
22291      }
22292
22293      // 4. Let code unit be the result of:
22294      var code_unit;
22295      if (utf16_be) {
22296        // utf-16be decoder flag is set
22297        //   (utf-16 lead byte << 8) + byte.
22298        code_unit = (utf16_lead_byte << 8) + bite;
22299      } else {
22300        // utf-16be decoder flag is unset
22301        //   (byte << 8) + utf-16 lead byte.
22302        code_unit = (bite << 8) + utf16_lead_byte;
22303      }
22304      // Then set utf-16 lead byte to null.
22305      utf16_lead_byte = null;
22306
22307      // 5. If utf-16 lead surrogate is not null, let lead surrogate
22308      // be utf-16 lead surrogate, set utf-16 lead surrogate to null,
22309      // and then run these substeps:
22310      if (utf16_lead_surrogate !== null) {
22311        var lead_surrogate = utf16_lead_surrogate;
22312        utf16_lead_surrogate = null;
22313
22314        // 1. If code unit is in the range U+DC00 to U+DFFF, return a
22315        // code point whose value is 0x10000 + ((lead surrogate −
22316        // 0xD800) << 10) + (code unit − 0xDC00).
22317        if (inRange(code_unit, 0xDC00, 0xDFFF)) {
22318          return 0x10000 + (lead_surrogate - 0xD800) * 0x400 +
22319              (code_unit - 0xDC00);
22320        }
22321
22322        // 2. Prepend the sequence resulting of converting code unit
22323        // to bytes using utf-16be decoder flag to stream and return
22324        // error.
22325        stream.prepend(convertCodeUnitToBytes(code_unit, utf16_be));
22326        return decoderError(fatal);
22327      }
22328
22329      // 6. If code unit is in the range U+D800 to U+DBFF, set utf-16
22330      // lead surrogate to code unit and return continue.
22331      if (inRange(code_unit, 0xD800, 0xDBFF)) {
22332        utf16_lead_surrogate = code_unit;
22333        return null;
22334      }
22335
22336      // 7. If code unit is in the range U+DC00 to U+DFFF, return
22337      // error.
22338      if (inRange(code_unit, 0xDC00, 0xDFFF))
22339        return decoderError(fatal);
22340
22341      // 8. Return code point code unit.
22342      return code_unit;
22343    };
22344  }
22345
22346  // 15.2.2 shared utf-16 encoder
22347  /**
22348   * @constructor
22349   * @implements {Encoder}
22350   * @param {boolean} utf16_be True if big-endian, false if little-endian.
22351   * @param {{fatal: boolean}} options
22352   */
22353  function UTF16Encoder(utf16_be, options) {
22354    var fatal = options.fatal;
22355    /**
22356     * @param {Stream} stream Input stream.
22357     * @param {number} code_point Next code point read from the stream.
22358     * @return {(number|!Array.<number>)} Byte(s) to emit.
22359     */
22360    this.handler = function(stream, code_point) {
22361      // 1. If code point is end-of-stream, return finished.
22362      if (code_point === end_of_stream)
22363        return finished;
22364
22365      // 2. If code point is in the range U+0000 to U+FFFF, return the
22366      // sequence resulting of converting code point to bytes using
22367      // utf-16be encoder flag.
22368      if (inRange(code_point, 0x0000, 0xFFFF))
22369        return convertCodeUnitToBytes(code_point, utf16_be);
22370
22371      // 3. Let lead be ((code point − 0x10000) >> 10) + 0xD800,
22372      // converted to bytes using utf-16be encoder flag.
22373      var lead = convertCodeUnitToBytes(
22374        ((code_point - 0x10000) >> 10) + 0xD800, utf16_be);
22375
22376      // 4. Let trail be ((code point − 0x10000) & 0x3FF) + 0xDC00,
22377      // converted to bytes using utf-16be encoder flag.
22378      var trail = convertCodeUnitToBytes(
22379        ((code_point - 0x10000) & 0x3FF) + 0xDC00, utf16_be);
22380
22381      // 5. Return a byte sequence of lead followed by trail.
22382      return lead.concat(trail);
22383    };
22384  }
22385
22386  // 15.3 utf-16be
22387  // 15.3.1 utf-16be decoder
22388  /** @param {{fatal: boolean}} options */
22389  encoders['UTF-16BE'] = function(options) {
22390    return new UTF16Encoder(true, options);
22391  };
22392  // 15.3.2 utf-16be encoder
22393  /** @param {{fatal: boolean}} options */
22394  decoders['UTF-16BE'] = function(options) {
22395    return new UTF16Decoder(true, options);
22396  };
22397
22398  // 15.4 utf-16le
22399  // 15.4.1 utf-16le decoder
22400  /** @param {{fatal: boolean}} options */
22401  encoders['UTF-16LE'] = function(options) {
22402    return new UTF16Encoder(false, options);
22403  };
22404  // 15.4.2 utf-16le encoder
22405  /** @param {{fatal: boolean}} options */
22406  decoders['UTF-16LE'] = function(options) {
22407    return new UTF16Decoder(false, options);
22408  };
22409
22410  // 15.5 x-user-defined
22411
22412  // 15.5.1 x-user-defined decoder
22413  /**
22414   * @constructor
22415   * @implements {Decoder}
22416   * @param {{fatal: boolean}} options
22417   */
22418  function XUserDefinedDecoder(options) {
22419    var fatal = options.fatal;
22420    /**
22421     * @param {Stream} stream The stream of bytes being decoded.
22422     * @param {number} bite The next byte read from the stream.
22423     * @return {?(number|!Array.<number>)} The next code point(s)
22424     *     decoded, or null if not enough data exists in the input
22425     *     stream to decode a complete code point.
22426     */
22427    this.handler = function(stream, bite) {
22428      // 1. If byte is end-of-stream, return finished.
22429      if (bite === end_of_stream)
22430        return finished;
22431
22432      // 2. If byte is an ASCII byte, return a code point whose value
22433      // is byte.
22434      if (isASCIIByte(bite))
22435        return bite;
22436
22437      // 3. Return a code point whose value is 0xF780 + byte − 0x80.
22438      return 0xF780 + bite - 0x80;
22439    };
22440  }
22441
22442  // 15.5.2 x-user-defined encoder
22443  /**
22444   * @constructor
22445   * @implements {Encoder}
22446   * @param {{fatal: boolean}} options
22447   */
22448  function XUserDefinedEncoder(options) {
22449    var fatal = options.fatal;
22450    /**
22451     * @param {Stream} stream Input stream.
22452     * @param {number} code_point Next code point read from the stream.
22453     * @return {(number|!Array.<number>)} Byte(s) to emit.
22454     */
22455    this.handler = function(stream, code_point) {
22456      // 1.If code point is end-of-stream, return finished.
22457      if (code_point === end_of_stream)
22458        return finished;
22459
22460      // 2. If code point is an ASCII code point, return a byte whose
22461      // value is code point.
22462      if (isASCIICodePoint(code_point))
22463        return code_point;
22464
22465      // 3. If code point is in the range U+F780 to U+F7FF, return a
22466      // byte whose value is code point − 0xF780 + 0x80.
22467      if (inRange(code_point, 0xF780, 0xF7FF))
22468        return code_point - 0xF780 + 0x80;
22469
22470      // 4. Return error with code point.
22471      return encoderError(code_point);
22472    };
22473  }
22474
22475  /** @param {{fatal: boolean}} options */
22476  encoders['x-user-defined'] = function(options) {
22477    return new XUserDefinedEncoder(options);
22478  };
22479  /** @param {{fatal: boolean}} options */
22480  decoders['x-user-defined'] = function(options) {
22481    return new XUserDefinedDecoder(options);
22482  };
22483
22484  if (!global['TextEncoder'])
22485    global['TextEncoder'] = TextEncoder;
22486  if (!global['TextDecoder'])
22487    global['TextDecoder'] = TextDecoder;
22488
22489  if (typeof module !== "undefined" && module.exports) {
22490    module.exports = {
22491      TextEncoder: global['TextEncoder'],
22492      TextDecoder: global['TextDecoder'],
22493      EncodingIndexes: global["encoding-indexes"]
22494    };
22495  }
22496}(this));
22497//
22498// this is a patch that provides fetch with keep alive support when making
22499// synchronous requests during unload that finish in a 0 status, a problem
22500// introduced by Chrome 80
22501//
22502(function () {
22503    var origInitByEnvironment;
22504
22505    //
22506    // only apply the patch if this browser has the capability to do that type
22507    // of request
22508    //
22509    // have to support fetch() and keepalive flag to continue
22510    if (typeof fetch !== "function" || ! ("keepalive" in new Request(""))) {
22511        return;
22512    }
22513
22514    //
22515    // because the type of request used for a particular LRS depends on the
22516    // URL (CORS, etc.) of the endpoint we can't determine the type of request
22517    // and whether we can do anything about it until construction of the LRS
22518    // itself which does an Environment initialization procedure, so this
22519    // patch is overloading that initialization so that we can wrap the request
22520    // method that is assigned into the LRS object with a function that can
22521    // detect and handle the specific case we're after, and passthrough any
22522    // case we're not attempting to fix
22523    //
22524    origInitByEnvironment = TinCan.LRS.prototype._initByEnvironment;
22525
22526    TinCan.LRS.prototype._initByEnvironment = function (cfg) {
22527        // _makeRequest is apparently not called with `this` bound correctly
22528        var lrs = this,
22529            origMakeRequest;
22530
22531        // call the original to set up the LRS object (`this`) as it would normally
22532        origInitByEnvironment.call(this, cfg);
22533
22534        //
22535        // after the original call the `_makeRequest` function will have been set up
22536        // to do what it needs to do, that function will return the XHR object when
22537        // it is called in a synchronous manner, which gives us a way to override
22538        // the behavior when we detect the condition we care about, specifically a
22539        // return status of 0
22540        //
22541        origMakeRequest = this._makeRequest;
22542
22543        this._makeRequest = function (_fullUrl, headers, cfg) {
22544            var origResult = origMakeRequest.apply(lrs, arguments),
22545                pairs = [],
22546                fullUrl = _fullUrl,
22547                fetchRequestCfg;
22548
22549            // not sure how, but we got an async request which this patch isn't
22550            // trying to solve for, so just return
22551            if (typeof cfg.callback !== "undefined") {
22552                return origResult;
22553            }
22554
22555            // this patch was originally introduced to handle the case of synchronous
22556            // requests being blocked during unload, and it was assumed that it was
22557            // okay to also attempt a fetch version of the request when the response
22558            // was a 0 status indicating offline because it would be better to have
22559            // it succeed if it were back online for instance, but by letting this
22560            // fall through to the fetch request means there is no way to capture
22561            // an original error in the non-unloading case (IOW, normal processing)
22562            // which means that requests that indicate offline during normal processing
22563            // don't result in an error being indicated and so the updates would
22564            // get lost, so only allow falling through to the fetch version when
22565            // we're in the process of unloading
22566            //
22567            if (! blnUnloading) {
22568                return origResult;
22569            }
22570
22571            // original request is unrecognized, succeeded, or doesn't indicate
22572            // a network error (zero status) so nothing else to do here
22573            //
22574            // the `err` property of the result indicates the HTTP status returned
22575            // from the request, a 0 status code is returned for the condition we're
22576            // attempting to fix with this patch, but could be returned in other cases
22577            // where trying a `fetch` is fine, but isn't likely to work either (like
22578            // network failures, etc.)
22579            //
22580            if (typeof origResult.err === "undefined" || origResult.err === null || origResult.err !== 0) {
22581                return origResult;
22582            }
22583
22584            /*
22585            The straightforward way to achieve this should be to just construct
22586            a request using effectively the same values as we sent for the native
22587            request, but formatted in such a way that we can pass it to fetch which
22588            is what the original attempt, still shown here, was which mostly worked,
22589            but owing to another bug in Chrome that prevents CORS requests to be
22590            sent in the manner below (specifically with `keepalive`) 
22590we couldn't use
22591            this method.
22592
22593            //
22594            // unfortunately logic in the original `nativeRequest` needs to be
22595            // duplicated here to achieve the same request
22596            //
22597            for (prop in cfg.params) {
22598                if (cfg.params.hasOwnProperty(prop)) {
22599                    pairs.push(prop + "=" + encodeURIComponent(cfg.params[prop]));
22600                }
22601            }
22602
22603            if (pairs.length > 0) {
22604                fullUrl += "?" + pairs.join("&");
22605            }
22606
22607            //
22608            // the rest of this is our patch to send the newer type of request
22609            //
22610            fetchRequestCfg = {
22611                mode: "cors",
22612                cache: "no-cache",
22613                credentials: "include",
22614                keepalive: true,
22615
22616                method: cfg.method,
22617                headers: headers
22618            }
22619            if (cfg.data) {
22620                fetchRequestCfg.body = cfg.data;
22621            }
22622            */
22623
22624            /*
22625            This is the alternate method being used as described above, to avoid
22626            the CORS problem switch to an "IE Mode request" set up and use that
22627            but still within the confines of a keep alive fetch request
22628            */
22629
22630            //
22631            // unfortunately logic in the original `nativeRequest` needs to be
22632            // duplicated here to achieve the same request
22633            //
22634            for (prop in cfg.params) {
22635                if (cfg.params.hasOwnProperty(prop)) {
22636                    pairs.push(prop + "=" + encodeURIComponent(cfg.params[prop]));
22637                }
22638            }
22639
22640            fullUrl = lrs._IEModeConversion(fullUrl, headers, pairs, cfg);
22641
22642
22643            //
22644            // the rest of this is our patch to send the newer type of request
22645            //
22646            fetchRequestCfg = {
22647                mode: "cors",
22648                cache: "no-cache",
22649                credentials: "include",
22650                keepalive: true,
22651
22652                method: cfg.method,
22653                headers: {
22654                    //
22655                    // TinCanJS' IE mode conversion routine alters the `headers` object
22656                    // and copies the existing headers into the body content, but doesn't
22657                    // remove the copied headers from the object so we can't wholesale
22658                    // use what is left, so only pull out the Content-Type since it was
22659                    // changed by that function, any other header would trigger a CORS
22660                    // pre-flight request here which isn't currently (as of Chrome 79)
22661                    // supported for `fetch` requests
22662                    //
22663                    "Content-Type": headers["Content-Type"]
22664                },
22665                body: cfg.data
22666            }
22667
22668            try {
22669                //
22670                // we have promise handlers here but in the primary case that this code targets,
22671                // synchronous requests during exit, those handlers will never get hit so these
22672                // log messages are likely not to be seen
22673                //
22674                fetch(fullUrl, fetchRequestCfg).then(
22675                    function (response) {
22676                        lrs.log("Overridden request Fetch with KeepAlive finished with status " + response.status + ":" + response.statusText);
22677                    }
22678                )
22679                ["catch"]( // IE8 has .catch as reserved, so use bracket notation
22680                    function (e) {
22681                        lrs.log("Overridden request Fetch with KeepAlive returned error: " + e.message);
22682                    }
22683                );
22684
22685                //
22686                // We're making an assumption here that if we can fire this request, it
22687                // will complete successfully. Since we couldn't deal with a failure
22688                // anyway, there's no point in handling the Promise returned by the
22689                // fetch call.
22690                //
22691                // Not returning an object that is normally recognized does prevent
22692                // subsequent requests from not happening, so even though we can't detect
22693                // success/failure, this needs to at least act like success so that the
22694                // other requests are made
22695                //
22696                return {
22697                    err: null
22698                };
22699            }
22700            catch (e) {
22701                // This will likely never get hit, but we don't want any error to bubble up from this method
22702                lrs.log("Overridden request Fetch with KeepAlive threw error: " + e.message);
22703
22704                return {
22705                    err: e.message,
22706                    xhr: null
22707                };
22708            }
22709        };
22710    };
22711}());
22712/*! http://mths.be/punycode v1.2.3 by @mathias */
22713;(function(root) {
22714
22715	/** Detect free variables */
22716	var freeExports = typeof exports == 'object' && exports;
22717	var freeModule = typeof module == 'object' && module &&
22718		module.exports == freeExports && module;
22719	var freeGlobal = typeof global == 'object' && global;
22720	if (freeGlobal.global === freeGlobal || freeGlobal.window === freeGlobal) {
22721		root = freeGlobal;
22722	}
22723
22724	/**
22725	 * The `punycode` object.
22726	 * @name punycode
22727	 * @type Object
22728	 */
22729	var punycode,
22730
22731	/** Highest positive signed 32-bit float value */
22732	maxInt = 2147483647, // aka. 0x7FFFFFFF or 2^31-1
22733
22734	/** Bootstring parameters */
22735	base = 36,
22736	tMin = 1,
22737	tMax = 26,
22738	skew = 38,
22739	damp = 700,
22740	initialBias = 72,
22741	initialN = 128, // 0x80
22742	delimiter = '-', // '\x2D'
22743
22744	/** Regular expressions */
22745	regexPunycode = /^xn--/,
22746	regexNonASCII = /[^ -~]/, // unprintable ASCII chars + non-ASCII chars
22747	regexSeparators = /\x2E|\u3002|\uFF0E|\uFF61/g, // RFC 3490 separators
22748
22749	/** Error messages */
22750	errors = {
22751		'overflow': 'Overflow: input needs wider integers to process',
22752		'not-basic': 'Illegal input >= 0x80 (not a basic code point)',
22753		'invalid-input': 'Invalid input'
22754	},
22755
22756	/** Convenience shortcuts */
22757	baseMinusTMin = base - tMin,
22758	floor = Math.floor,
22759	stringFromCharCode = String.fromCharCode,
22760
22761	/** Temporary variable */
22762	key;
22763
22764	/*--------------------------------------------------------------------------*/
22765
22766	/**
22767	 * A generic error utility function.
22768	 * @private
22769	 * @param {String} type The error type.
22770	 * @returns {Error} Throws a `RangeError` with the applicable error message.
22771	 */
22772	function error(type) {
22773		throw RangeError(errors[type]);
22774	}
22775
22776	/**
22777	 * A generic `Array#map` utility function.
22778	 * @private
22779	 * @param {Array} array The array to iterate over.
22780	 * @param {Function} callback The function that gets called for every array
22781	 * item.
22782	 * @returns {Array} A new array of values returned by the callback function.
22783	 */
22784	function map(array, fn) {
22785		var length = array.length;
22786		while (length--) {
22787			array[length] = fn(array[length]);
22788		}
22789		return array;
22790	}
22791
22792	/**
22793	 * A simple `Array#map`-like wrapper to work with domain name strings.
22794	 * @private
22795	 * @param {String} domain The domain name.
22796	 * @param {Function} callback The function that gets called for every
22797	 * character.
22798	 * @returns {Array} A new string of characters returned by the callback
22799	 * function.
22800	 */
22801	function mapDomain(string, fn) {
22802		return map(string.split(regexSeparators), fn).join('.');
22803	}
22804
22805	/**
22806	 * Creates an array containing the numeric code points of each Unicode
22807	 * character in the string. While JavaScript uses UCS-2 internally,
22808	 * this function will convert a pair of surrogate halves (each of which
22809	 * UCS-2 exposes as separate characters) into a single code point,
22810	 * matching UTF-16.
22811	 * @see `punycode.ucs2.encode`
22812	 * @see <http://mathiasbynens.be/notes/javascript-encoding>
22813	 * @memberOf punycode.ucs2
22814	 * @name decode
22815	 * @param {String} string The Unicode input string (UCS-2).
22816	 * @returns {Array} The new array of code points.
22817	 */
22818	function ucs2decode(string) {
22819		var output = [],
22820		    counter = 0,
22821		    length = string.length,
22822		    value,
22823		    extra;
22824		while (counter < length) {
22825			value = string.charCodeAt(counter++);
22826			if (value >= 0xD800 && value <= 0xDBFF && counter < length) {
22827				// high surrogate, and there is a next character
22828				extra = string.charCodeAt(counter++);
22829				if ((extra & 0xFC00) == 0xDC00) { // low surrogate
vendor: 9,410 bytes, lines 22830-23175
22830					output.push(((value & 0x3FF) << 10) + (extra & 0x3FF) + 0x10000);
22831				} else {
22832					// unmatched surrogate; only append this code unit, in case the next
22833					// code unit is the high surrogate of a surrogate pair
22834					output.push(value);
22835					counter--;
22836				}
22837			} else {
22838				output.push(value);
22839			}
22840		}
22841		return output;
22842	}
22843
22844	/**
22845	 * Creates a string based on an array of numeric code points.
22846	 * @see `punycode.ucs2.decode`
22847	 * @memberOf punycode.ucs2
22848	 * @name encode
22849	 * @param {Array} codePoints The array of numeric code points.
22850	 * @returns {String} The new Unicode string (UCS-2).
22851	 */
22852	function ucs2encode(array) {
22853		return map(array, function(value) {
22854			var output = '';
22855			if (value > 0xFFFF) {
22856				value -= 0x10000;
22857				output += stringFromCharCode(value >>> 10 & 0x3FF | 0xD800);
22858				value = 0xDC00 | value & 0x3FF;
22859			}
22860			output += stringFromCharCode(value);
22861			return output;
22862		}).join('');
22863	}
22864
22865	/**
22866	 * Converts a basic code point into a digit/integer.
22867	 * @see `digitToBasic()`
22868	 * @private
22869	 * @param {Number} codePoint The basic numeric code point value.
22870	 * @returns {Number} The numeric value of a basic code point (for use in
22871	 * representing integers) in the range `0` to `base - 1`, or `base` if
22872	 * the code point does not represent a value.
22873	 */
22874	function basicToDigit(codePoint) {
22875		if (codePoint - 48 < 10) {
22876			return codePoint - 22;
22877		}
22878		if (codePoint - 65 < 26) {
22879			return codePoint - 65;
22880		}
22881		if (codePoint - 97 < 26) {
22882			return codePoint - 97;
22883		}
22884		return base;
22885	}
22886
22887	/**
22888	 * Converts a digit/integer into a basic code point.
22889	 * @see `basicToDigit()`
22890	 * @private
22891	 * @param {Number} digit The numeric value of a basic code point.
22892	 * @returns {Number} The basic code point whose value (when used for
22893	 * representing integers) is `digit`, which needs to be in the range
22894	 * `0` to `base - 1`. If `flag` is non-zero, the uppercase form is
22895	 * used; else, the lowercase form is used. The behavior is undefined
22896	 * if `flag` is non-zero and `digit` has no uppercase form.
22897	 */
22898	function digitToBasic(digit, flag) {
22899		//  0..25 map to ASCII a..z or A..Z
22900		// 26..35 map to ASCII 0..9
22901		return digit + 22 + 75 * (digit < 26) - ((flag != 0) << 5);
22902	}
22903
22904	/**
22905	 * Bias adaptation function as per section 3.4 of RFC 3492.
22906	 * http://tools.ietf.org/html/rfc3492#section-3.4
22907	 * @private
22908	 */
22909	function adapt(delta, numPoints, firstTime) {
22910		var k = 0;
22911		delta = firstTime ? floor(delta / damp) : delta >> 1;
22912		delta += floor(delta / numPoints);
22913		for (/* no initialization */; delta > baseMinusTMin * tMax >> 1; k += base) {
22914			delta = floor(delta / baseMinusTMin);
22915		}
22916		return floor(k + (baseMinusTMin + 1) * delta / (delta + skew));
22917	}
22918
22919	/**
22920	 * Converts a Punycode string of ASCII-only symbols to a string of Unicode
22921	 * symbols.
22922	 * @memberOf punycode
22923	 * @param {String} input The Punycode string of ASCII-only symbols.
22924	 * @returns {String} The resulting string of Unicode symbols.
22925	 */
22926	function decode(input) {
22927		// Don't use UCS-2
22928		var output = [],
22929		    inputLength = input.length,
22930		    out,
22931		    i = 0,
22932		    n = initialN,
22933		    bias = initialBias,
22934		    basic,
22935		    j,
22936		    index,
22937		    oldi,
22938		    w,
22939		    k,
22940		    digit,
22941		    t,
22942		    length,
22943		    /** Cached calculation results */
22944		    baseMinusT;
22945
22946		// Handle the basic code points: let `basic` be the number of input code
22947		// points before the last delimiter, or `0` if there is none, then copy
22948		// the first basic code points to the output.
22949
22950		basic = input.lastIndexOf(delimiter);
22951		if (basic < 0) {
22952			basic = 0;
22953		}
22954
22955		for (j = 0; j < basic; ++j) {
22956			// if it's not a basic code point
22957			if (input.charCodeAt(j) >= 0x80) {
22958				error('not-basic');
22959			}
22960			output.push(input.charCodeAt(j));
22961		}
22962
22963		// Main decoding loop: start just after the last delimiter if any basic code
22964		// points were copied; start at the beginning otherwise.
22965
22966		for (index = basic > 0 ? basic + 1 : 0; index < inputLength; /* no final expression */) {
22967
22968			// `index` is the index of the next character to be consumed.
22969			// Decode a generalized variable-length integer into `delta`,
22970			// which gets added to `i`. The overflow checking is easier
22971			// if we increase `i` as we go, then subtract off its starting
22972			// value at the end to obtain `delta`.
22973			for (oldi = i, w = 1, k = base; /* no condition */; k += base) {
22974
22975				if (index >= inputLength) {
22976					error('invalid-input');
22977				}
22978
22979				digit = basicToDigit(input.charCodeAt(index++));
22980
22981				if (digit >= base || digit > floor((maxInt - i) / w)) {
22982					error('overflow');
22983				}
22984
22985				i += digit * w;
22986				t = k <= bias ? tMin : (k >= bias + tMax ? tMax : k - bias);
22987
22988				if (digit < t) {
22989					break;
22990				}
22991
22992				baseMinusT = base - t;
22993				if (w > floor(maxInt / baseMinusT)) {
22994					error('overflow');
22995				}
22996
22997				w *= baseMinusT;
22998
22999			}
23000
23001			out = output.length + 1;
23002			bias = adapt(i - oldi, out, oldi == 0);
23003
23004			// `i` was supposed to wrap around from `out` to `0`,
23005			// incrementing `n` each time, so we'll fix that now:
23006			if (floor(i / out) > maxInt - n) {
23007				error('overflow');
23008			}
23009
23010			n += floor(i / out);
23011			i %= out;
23012
23013			// Insert `n` at position `i` of the output
23014			output.splice(i++, 0, n);
23015
23016		}
23017
23018		return ucs2encode(output);
23019	}
23020
23021	/**
23022	 * Converts a string of Unicode symbols to a Punycode string of ASCII-only
23023	 * symbols.
23024	 * @memberOf punycode
23025	 * @param {String} input The string of Unicode symbols.
23026	 * @returns {String} The resulting Punycode string of ASCII-only symbols.
23027	 */
23028	function encode(input) {
23029		var n,
23030		    delta,
23031		    handledCPCount,
23032		    basicLength,
23033		    bias,
23034		    j,
23035		    m,
23036		    q,
23037		    k,
23038		    t,
23039		    currentValue,
23040		    output = [],
23041		    /** `inputLength` will hold the number of code points in `input`. */
23042		    inputLength,
23043		    /** Cached calculation results */
23044		    handledCPCountPlusOne,
23045		    baseMinusT,
23046		    qMinusT;
23047
23048		// Convert the input in UCS-2 to Unicode
23049		input = ucs2decode(input);
23050
23051		// Cache the length
23052		inputLength = input.length;
23053
23054		// Initialize the state
23055		n = initialN;
23056		delta = 0;
23057		bias = initialBias;
23058
23059		// Handle the basic code points
23060		for (j = 0; j < inputLength; ++j) {
23061			currentValue = input[j];
23062			if (currentValue < 0x80) {
23063				output.push(stringFromCharCode(currentValue));
23064			}
23065		}
23066
23067		handledCPCount = basicLength = output.length;
23068
23069		// `handledCPCount` is the number of code points that have been handled;
23070		// `basicLength` is the number of basic code points.
23071
23072		// Finish the basic string - if it is not empty - with a delimiter
23073		if (basicLength) {
23074			output.push(delimiter);
23075		}
23076
23077		// Main encoding loop:
23078		while (handledCPCount < inputLength) {
23079
23080			// All non-basic code points < n have been handled already. Find the next
23081			// larger one:
23082			for (m = maxInt, j = 0; j < inputLength; ++j) {
23083				currentValue = input[j];
23084				if (currentValue >= n && currentValue < m) {
23085					m = currentValue;
23086				}
23087			}
23088
23089			// Increase `delta` enough to advance the decoder's <n,i> state to <m,0>,
23090			// but guard against overflow
23091			handledCPCountPlusOne = handledCPCount + 1;
23092			if (m - n > floor((maxInt - delta) / handledCPCountPlusOne)) {
23093				error('overflow');
23094			}
23095
23096			delta += (m - n) * handledCPCountPlusOne;
23097			n = m;
23098
23099			for (j = 0; j < inputLength; ++j) {
23100				currentValue = input[j];
23101
23102				if (currentValue < n && ++delta > maxInt) {
23103					error('overflow');
23104				}
23105
23106				if (currentValue == n) {
23107					// Represent delta as a generalized variable-length integer
23108					for (q = delta, k = base; /* no condition */; k += base) {
23109						t = k <= bias ? tMin : (k >= bias + tMax ? tMax : k - bias);
23110						if (q < t) {
23111							break;
23112						}
23113						qMinusT = q - t;
23114						baseMinusT = base - t;
23115						output.push(
23116							stringFromCharCode(digitToBasic(t + qMinusT % baseMinusT, 0))
23117						);
23118						q = floor(qMinusT / baseMinusT);
23119					}
23120
23121					output.push(stringFromCharCode(digitToBasic(q, 0)));
23122					bias = adapt(delta, handledCPCountPlusOne, handledCPCount == basicLength);
23123					delta = 0;
23124					++handledCPCount;
23125				}
23126			}
23127
23128			++delta;
23129			++n;
23130
23131		}
23132		return output.join('');
23133	}
23134
23135	/**
23136	 * Converts a Punycode string representing a domain name to Unicode. Only the
23137	 * Punycoded parts of the domain name will be converted, i.e. it doesn't
23138	 * matter if you call it on a string that has already been converted to
23139	 * Unicode.
23140	 * @memberOf punycode
23141	 * @param {String} domain The Punycode domain name to convert to Unicode.
23142	 * @returns {String} The Unicode representation of the given Punycode
23143	 * string.
23144	 */
23145	function toUnicode(domain) {
23146		return mapDomain(domain, function(string) {
23147			return regexPunycode.test(string)
23148				? decode(string.slice(4).toLowerCase())
23149				: string;
23150		});
23151	}
23152
23153	/**
23154	 * Converts a Unicode string representing a domain name to Punycode. Only the
23155	 * non-ASCII parts of the domain name will be converted, i.e. it doesn't
23156	 * matter if you call it with a domain that's already in ASCII.
23157	 * @memberOf punycode
23158	 * @param {String} domain The domain name to convert, as a Unicode string.
23159	 * @returns {String} The Punycode representation of the given domain name.
23160	 */
23161	function toASCII(domain) {
23162		return mapDomain(domain, function(string) {
23163			return regexNonASCII.test(string)
23164				? 'xn--' + encode(string)
23165				: string;
23166		});
23167	}
23168
23169	/*--------------------------------------------------------------------------*/
23170
23171	/** Define the public API */
23172	punycode = {
23173		/**
23174		 * A string representing the current Punycode.js version number.
23175		 * @memberOf punycode
23176		 * @type String
23177		 */
23178		'version': '1.2.3',
23179		/**
23180		 * An object of methods to convert from JavaScript's internal character
23181		 * representation (UCS-2) to Unicode code points, and back.
23182		 * @see <http://mathiasbynens.be/notes/javascript-encoding>
23183		 * @memberOf punycode
23184		 * @type Object
23185		 */
23186		'ucs2': {
23187			'decode': ucs2decode,
23188			'encode': ucs2encode
23189		},
23190		'decode': decode,
23191		'encode': encode,
23192		'toASCII': toASCII,
23193		'toUnicode': toUnicode
23194	};
23195
23196	/** Expose `punycode` */
23197	// Some AMD build optimizers, like r.js, check for specific condition patterns
23198	// like the following:
23199	if (
23200		typeof define == 'function' &&
23201		typeof define.amd == 'object' &&
23202		define.amd
23203	) {
23204		define(function() {
23205			return punycode;
23206		});
23207	}	else if (freeExports && !freeExports.nodeType) {
23208		if (freeModule) { // in Node.js or RingoJS v0.8.0+
23209			freeModule.exports = punycode;
23210		} else { // in Narwhal or RingoJS v0.7.0-
23211			for (key in punycode) {
23212				punycode.hasOwnProperty(key) && (freeExports[key] = punycode[key]);
23213			}
23214		}
23215	} else { // in Rhino or a web browser
23216		root.punycode = punycode;
23217	}
23218
23219}(this));
23220/*!
23221 * URI.js - Mutating URLs
23222 *
23223 * Version: 1.14.2
23224 *
23225 * Author: Rodney Rehm
23226 * Web: http://medialize.github.io/URI.js/
23227 *
23228 * Licensed under
23229 *   MIT License http://www.opensource.org/licenses/mit-license
23230 *   GPL v3 http://opensource.org/licenses/GPL-3.0
23231 *
23232 */
23233(function (root, factory) {
23234  'use strict';
23235  // https://github.com/umdjs/umd/blob/master/returnExports.js
23236  if (typeof exports === 'object') {
23237    // Node
23238    module.exports = factory(require('./punycode'), require('./IPv6'), require('./SecondLevelDomains'));
23239  } else if (typeof define === 'function' && define.amd) {
23240    // AMD. Register as an anonymous module.
23241    define(['./punycode', './IPv6', './SecondLevelDomains'], factory);
23242  } else {
23243    // Browser globals (root is window)
23244    root.URI = factory(root.punycode, root.IPv6, root.SecondLevelDomains, root);
23245  }
23246}(this, function (punycode, IPv6, SLD, root) {
23247  'use strict';
23248  /*global location, escape, unescape */
23249  // FIXME: v2.0.0 renamce non-camelCase properties to uppercase
23250  /*jshint camelcase: false */
23251
23252  // save current URI variable, if any
23253  var _URI = root && root.URI;
23254
23255  function URI(url, base) {
23256    // Allow instantiation without the 'new' keyword
23257    if (!(this instanceof URI)) {
23258      return new URI(url, base);
23259    }
23260
23261    if (url === undefined) {
23262      if (arguments.length) {
23263        throw new TypeError('undefined is not a valid argument for URI');
23264      }
23265
23266      if (typeof location !== 'undefined') {
23267        url = location.href + '';
23268      } else {
23269        url = '';
23270      }
23271    }
23272
23273    this.href(url);
23274
23275    // resolve to base according to http://dvcs.w3.org/hg/url/raw-file/tip/Overview.html#constructor
23276    if (base !== undefined) {
23277      return this.absoluteTo(base);
23278    }
23279
23280    return this;
23281  }
23282
23283  URI.version = '1.14.2';
23284
23285  var p = URI.prototype;
23286  var hasOwn = Object.prototype.hasOwnProperty;
23287
23288  function escapeRegEx(string) {
23289    // https://github.com/medialize/URI.js/commit/85ac21783c11f8ccab06106dba9735a31a86924d#commitcomment-821963
23290    return string.replace(/([.*+?^=!:${}()|[\]\/\\])/g, '\\$1');
23291  }
23292
23293  function getType(value) {
23294    // IE8 doesn't return [Object Undefined] but [Object Object] for undefined value
23295    if (value === undefined) {
23296      return 'Undefined';
23297    }
23298
23299    return String(Object.prototype.toString.call(value)).slice(8, -1);
23300  }
23301
23302  function isArray(obj) {
23303    return getType(obj) === 'Array';
23304  }
23305
23306  function filterArrayValues(data, value) {
23307    var lookup = {};
23308    var i, length;
23309
23310    if (isArray(value)) {
23311      for (i = 0, length = value.length; i < length; i++) {
23312        lookup[value[i]] = true;
23313      }
23314    } else {
23315      lookup[value] = true;
23316    }
23317
23318    for (i = 0, length = data.length; i < length; i++) {
23319      if (lookup[data[i]] !== undefined) {
23320        data.splice(i, 1);
23321        length--;
23322        i--;
23323      }
23324    }
23325
23326    return data;
23327  }
23328
23329  function arrayContains(list, value) {
23330    var i, length;
23331
23332    // value may be string, number, array, regexp
23333    if (isArray(value)) {
23334      // Note: this can be optimized to O(n) (instead of current O(m * n))
23335      for (i = 0, length = value.length; i < length; i++) {
23336        if (!arrayContains(list, value[i])) {
23337          return false;
23338        }
23339      }
23340
23341      return true;
23342    }
23343
23344    var _type = getType(value);
23345    for (i = 0, length = list.length; i < length; i++) {
23346      if (_type === 'RegExp') {
23347        if (typeof list[i] === 'string' && list[i].match(value)) {
23348          return true;
23349        }
23350      } else if (list[i] === value) {
23351        return true;
23352      }
23353    }
23354
23355    return false;
23356  }
23357
23358  function arraysEqual(one, two) {
23359    if (!isArray(one) || !isArray(two)) {
23360      return false;
23361    }
23362
23363    // arrays can't be equal if they have different amount of content
23364    if (one.length !== two.length) {
23365      return false;
23366    }
23367
23368    one.sort();
23369    two.sort();
23370
23371    for (var i = 0, l = one.length; i < l; i++) {
23372      if (one[i] !== two[i]) {
23373        return false;
23374      }
23375    }
23376
23377    return true;
23378  }
23379
23380  URI._parts = function() {
23381    return {
23382      protocol: null,
23383      username: null,
23384      password: null,
23385      hostname: null,
23386      urn: null,
23387      port: null,
23388      path: null,
23389      query: null,
23390      fragment: null,
23391      // state
23392      duplicateQueryParameters: URI.duplicateQueryParameters,
23393      escapeQuerySpace: URI.escapeQuerySpace
23394    };
23395  };
23396  // state: allow duplicate query parameters (a=1&a=1)
23397  URI.duplicateQueryParameters = false;
23398  // state: replaces + with %20 (space in query strings)
23399  URI.escapeQuerySpace = true;
23400  // static properties
23401  URI.protocol_expression = /^[a-z][a-z0-9.+-]*$/i;
23402  URI.idn_expression = /[^a-z0-9\.-]/i;
23403  URI.punycode_expression = /(xn--)/i;
23404  // well, 333.444.555.666 matches, but it sure ain't no IPv4 - do we care?
23405  URI.ip4_expression = /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/;
23406  // credits to Rich Brown
23407  // source: http://forums.intermapper.com/viewtopic.php?p=1096#1096
23408  // specification: http://www.ietf.org/rfc/rfc4291.txt
23409  URI.ip6_expression = /^\s*((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)))(%.+)?\s*$/;
23410  // expression used is "gruber revised" (@gruber v2) determined to be the
23411  // best solution in a regex-golf we did a couple of ages ago at
23412  // * http://mathiasbynens.be/demo/url-regex
23413  // * http://rodneyrehm.de/t/url-regex.html
23414  URI.find_uri_expression = /\b((?:[a-z][\w-]+:(?:\/{1,3}|[a-z0-9%])|www\d{0,3}[.]|[a-z0-9.\-]+[.][a-z]{2,4}\/)(?:[^\s()<>]+|\(([^\s()<>]+|(\([^\s()<>]+\)))*\))+(?:\(([^\s()<>]+|(\([^\s()<>]+\)))*\)|[^\s`!()\[\]{};:'".,<>?«»]))/ig;
23415  URI.findUri = {
23416    // valid "scheme://" or "www."
23417    start: /\b(?:([a-z][a-z0-9.+-]*:\/\/)|www\.)/gi,
23418    // everything up to the next whitespace
23419    end: /[\s\r\n]|$/,
23420    // trim trailing punctuation captured by end RegExp
23421    trim: /[`!()\[\]{};:'".,<>?«»]+$/
23422  };
23423  // http://www.iana.org/assignments/uri-schemes.html
23424  // http://en.wikipedia.org/wiki/List_of_TCP_and_UDP_port_numbers#Well-known_ports
23425  URI.defaultPorts = {
23426    http: '80',
23427    https: '443',
23428    ftp: '21',
23429    gopher: '70',
23430    ws: '80',
23431    wss: '443'
23432  };
23433  // allowed hostname characters according to RFC 3986
23434  // ALPHA DIGIT "-" "." "_" "~" "!" "$" "&" "'" "(" ")" "*" "+" "," ";" "=" %encoded
23435  // I've never seen a (non-IDN) hostname other than: ALPHA DIGIT . -
23436  URI.invalid_hostname_characters = /[^a-zA-Z0-9\.-]/;
23437  // map DOM Elements to their URI attribute
23438  URI.domAttributes = {
23439    'a': 'href',
23440    'blockquote': 'cite',
23441    'link': 'href',
23442    'base': 'href',
23443    'script': 'src',
23444    'form': 'action',
23445    'img': 'src',
23446    'area': 'href',
23447    'iframe': 'src',
23448    'embed': 'src',
23449    'source': 'src',
23450    'track': 'src',
23451    'input': 'src', // but only if type="image"
23452    'audio': 'src',
23453    'video': 'src'
23454  };
23455  URI.getDomAttribute = function(node) {
23456    if (!node || !node.nodeName) {
23457      return undefined;
23458    }
23459
23460    var nodeName = node.nodeName.toLowerCase();
23461    // <input> should only expose src for type="image"
23462    if (nodeName === 'input' && node.type !== 'image') {
23463      return undefined;
23464    }
23465
23466    return URI.domAttributes[nodeName];
23467  };
23468
23469  function escapeForDumbFirefox36(value) {
23470    // https://github.com/medialize/URI.js/issues/91
23471    return escape(value);
23472  }
23473
23474  // encoding / decoding according to RFC3986
23475  function strictEncodeURIComponent(string) {
23476    // see https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/encodeURIComponent
23477    return encodeURIComponent(string)
23478      .replace(/[!'()*]/g, escapeForDumbFirefox36)
23479      .replace(/\*/g, '%2A');
23480  }
23481
23482  // Drawn from https://www.ietf.org/rfc/rfc3987.txt
23483  function _isIriCodePoint(point) {
23484    // Unless otherwise noted, all these hex ranges are defined in RFC 3987 under the
23485    // ucschar portion of the grammar.
23486    return (
23487        point === 0x00002d || point === 0x0002E || // -, .
23488        point === 0x00005F || point === 0x0007E || // _, ~
23489        (point >= 0x000030 && point < 0x000040) || // ASCII Digits
23490        (point >= 0x000041 && point < 0x00005B) || // ASCII Uppercase
23491        (point >= 0x000061 && point < 0x00007B) || // ASCII Lowercase
23492        (point >= 0x0000A0 && point < 0x00D800) ||
23493        (point >= 0x00E000 && point < 0x00F8FF) || // iprivate
23494        (point >= 0x00F900 && point < 0x00FDD0) ||
23495        (point >= 0x00FDF0 && point < 0x00FFF0) ||
23496        (point >= 0x010000 && point < 0x01FFFE) ||
23497        (point >= 0x020000 && point < 0x02FFFE) ||
23498        (point >= 0x030000 && point < 0x03FFFE) ||
23499        (point >= 0x040000 && point < 0x04FFFE) ||
23500        (point >= 0x050000 && point < 0x05FFFE) ||
23501        (point >= 0x060000 && point < 0x06FFFE) ||
23502        (point >= 0x070000 && point < 0x07FFFE) ||
23503        (point >= 0x080000 && point < 0x08FFFE) ||
23504        (point >= 0x090000 && point < 0x09FFFE) ||
23505        (point >= 0x0A0000 && point < 0x0AFFFE) ||
23506        (point >= 0x0B0000 && point < 0x0BFFFE) ||
23507        (point >= 0x0C0000 && point < 0x0CFFFE) ||
23508        (point >= 0x0D0000 && point < 0x0DFFFE) ||
23509        (point >= 0x0E0000 && point < 0x0EFFFE) ||
23510        (point >= 0x0F0000 && point < 0x0FFFFE) || // iprivate
23511        (point >= 0x100000 && point < 0x10FFFE)    // iprivate
23512      );
23513  }
23514
23515  // IRIs are "unicode" (i.e., they use UTF-8 for %-encoding) URIs, but with a far greater range of
23516  // non-ASCII characters that are allowed. Note that ISO-8859 IRIs are an impossibility; ISO-8859
23517  // cannot represent the full range of unicode characters.
23518  function encodeIRIComponent(string) {
23519    var inputCodePoints = punycode.ucs2.decode(string);
23520    var output = '';
23521    for (var i = 0; i < inputCodePoints.length; i++) {
23522      var codePoint = inputCodePoints[i];
23523      if (_isIriCodePoint(codePoint)) {
23524        output += punycode.ucs2.encode([codePoint]);
23525      } else {
23526        var asString = punycode.ucs2.encode([codePoint]);
23527        output += strictEncodeURIComponent(asString);
23528      }
23529    }
23530    return output;
23531  }
23532
23533  function recodeIRIHostname(string) {
23534    if (URI.punycode_expression.test(string))
23535    {
23536      string = punycode.toUnicode(string);
23537    }
23538    return encodeIRIComponent(string);
23539  }
23540
23541  URI._defaultRecodeHostname = punycode ? punycode.toASCII : function(string) { return string; };
23542  URI.iso8859 = function() {
23543    URI.recodeHostname = URI._defaultRecodeHostname;
23544    URI.encode = escape;
23545    URI.decode = unescape;
23546  };
23547  URI.unicode = function() {
23548    URI.recodeHostname = URI._defaultRecodeHostname;
23549    URI.encode = strictEncodeURIComponent;
23550    URI.decode = decodeURIComponent;
23551  };
23552  URI.iri = function() {
23553    URI.recodeHostname = recodeIRIHostname;
23554    URI.encode = encodeIRIComponent;
23555    URI.decode = decodeURIComponent;
23556  };
23557  // By default, start in unicode mode.
23558  URI.unicode();
23559  URI.characters = {
23560    pathname: {
23561      encode: {
23562        // RFC3986 2.1: For consistency, URI producers and normalizers should
23563        // use uppercase hexadecimal digits for all percent-encodings.
23564        expression: /%(24|26|2B|2C|3B|3D|3A|40)/ig,
23565        map: {
23566          // -._~!'()*
23567          '%24': '$',
23568          '%26': '&',
23569          '%2B': '+',
23570          '%2C': ',',
23571          '%3B': ';',
23572          '%3D': '=',
23573          '%3A': ':',
23574          '%40': '@'
23575        }
23576      },
23577      decode: {
23578        expression: /[\/\?#]/g,
23579        map: {
23580          '/': '%2F',
23581          '?': '%3F',
23582          '#': '%23'
23583        }
23584      }
23585    },
23586    reserved: {
23587      encode: {
23588        // RFC3986 2.1: For consistency, URI producers and normalizers should
23589        // use uppercase hexadecimal digits for all percent-encodings.
23590        expression: /%(21|23|24|26|27|28|29|2A|2B|2C|2F|3A|3B|3D|3F|40|5B|5D)/ig,
23591        map: {
23592          // gen-delims
23593          '%3A': ':',
23594          '%2F': '/',
23595          '%3F': '?',
23596          '%23': '#',
23597          '%5B': '[',
23598          '%5D': ']',
23599          '%40': '@',
23600          // sub-delims
23601          '%21': '!',
23602          '%24': '$',
23603          '%26': '&',
23604          '%27': '\'',
23605          '%28': '(',
23606          '%29': ')',
23607          '%2A': '*',
23608          '%2B': '+',
23609          '%2C': ',',
23610          '%3B': ';',
23611          '%3D': '='
23612        }
23613      }
23614    },
23615    urnpath: {
23616      // The characters under `encode` are the characters called out by RFC 2141 as being acceptable
23617      // for usage in a URN. RFC2141 also calls out "-", ".", and "_" as acceptable characters, but
23618      // these aren't encoded by encodeURIComponent, so we don't have to call them out here. Also
23619      // note that the colon character is not featured in the encoding map; this is because URI.js
23620      // gives the colons in URNs semantic meaning as the delimiters of path segements, and so it
23621      // should not appear unencoded in a segment itself.
23622      // See also the note above about RFC3986 and capitalalized hex digits.
23623      encode: {
23624        expression: /%(21|24|27|28|29|2A|2B|2C|3B|3D|40)/ig,
23625        map: {
23626          '%21': '!',
23627          '%24': '$',
23628          '%27': '\'',
23629          '%28': '(',
23630          '%29': ')',
23631          '%2A': '*',
23632          '%2B': '+',
23633          '%2C': ',',
23634          '%3B': ';',
23635          '%3D': '=',
23636          '%40': '@'
23637        }
23638      },
23639      // These characters are the characters called out by RFC2141 as "reserved" characters that
23640      // should never appear in a URN, plus the colon character (see note above).
23641      decode: {
23642        expression: /[\/\?#:]/g,
23643        map: {
23644          '/': '%2F',
23645          '?': '%3F',
23646          '#': '%23',
23647          ':': '%3A'
23648        }
23649      }
23650    }
23651  };
23652  URI.encodeQuery = function(string, escapeQuerySpace) {
23653    var escaped = URI.encode(string + '');
23654    if (escapeQuerySpace === undefined) {
23655      escapeQuerySpace = URI.escapeQuerySpace;
23656    }
23657
23658    return escapeQuerySpace ? escaped.replace(/%20/g, '+') : escaped;
23659  };
23660  URI.decodeQuery = function(string, escapeQuerySpace) {
23661    string += '';
23662    if (escapeQuerySpace === undefined) {
23663      escapeQuerySpace = URI.escapeQuerySpace;
23664    }
23665
23666    try {
23667      return URI.decode(escapeQuerySpace ? string.replace(/\+/g, '%20') : string);
23668    } catch(e) {
23669      // we're not going to mess with weird encodings,
23670      // give up and return the undecoded original string
23671      // see https://github.com/medialize/URI.js/issues/87
23672      // see https://github.com/medialize/URI.js/issues/92
23673      return string;
23674    }
23675  };
23676  // generate encode/decode path functions
23677  var _parts = {'encode':'encode', 'decode':'decode'};
23678  var _part;
23679  var generateAccessor = function(_group, _part) {
23680    return function(string) {
23681      try {
23682        return URI[_part](string + '').replace(URI.characters[_group][_part].expression, function(c) {
23683          return URI.characters[_group][_part].map[c];
23684        });
23685      } catch (e) {
23686        // we're not going to mess with weird encodings,
23687        // give up and return the undecoded original string
23688        // see https://github.com/medialize/URI.js/issues/87
23689        // see https://github.com/medialize/URI.js/issues/92
23690        return string;
23691      }
23692    };
23693  };
23694
23695  for (_part in _parts) {
23696    URI[_part + 'PathSegment'] = generateAccessor('pathname', _parts[_part]);
23697    URI[_part + 'UrnPathSegment'] = generateAccessor('urnpath', _parts[_part]);
23698  }
23699
23700  var generateSegmentedPathFunction = function(_sep, _codingFuncName, _innerCodingFuncName) {
23701    return function(string) {
23702      // Why pass in names of functions, rather than the function objects themselves? The
23703      // definitions of some functions (but in particular, URI.decode) will occasionally change due
23704      // to URI.js having ISO8859 and Unicode modes. Passing in the name and getting it will ensure
23705      // that the functions we use here are "fresh".
23706      var actualCodingFunc;
23707      if (!_innerCodingFuncName) {
23708        actualCodingFunc = URI[_codingFuncName];
23709      } else {
23710        actualCodingFunc = function(string) {
23711          return URI[_codingFuncName](URI[_innerCodingFuncName](string));
23712        };
23713      }
23714
23715      var segments = (string + '').split(_sep);
23716
23717      for (var i = 0, length = segments.length; i < length; i++) {
23718        segments[i] = actualCodingFunc(segments[i]);
23719      }
23720
23721      return segments.join(_sep);
23722    };
23723  };
23724
23725  // This takes place outside the above loop because we don't want, e.g., encodeUrnPath functions.
23726  URI.decodePath = generateSegmentedPathFunction('/', 'decodePathSegment');
23727  URI.decodeUrnPath = generateSegmentedPathFunction(':', 'decodeUrnPathSegment');
23728  URI.recodePath = generateSegmentedPathFunction('/', 'encodePathSegment', 'decode');
23729  URI.recodeUrnPath = generateSegmentedPathFunction(':', 'encodeUrnPathSegment', 'decode');
23730
23731  URI.encodeReserved = generateAccessor('reserved', 'encode');
23732
23733  URI.parse = function(string, parts) {
23734    var pos;
23735    if (!parts) {
23736      parts = {};
23737    }
23738    // [protocol"://"[username[":"password]"@"]hostname[":"port]"/"?][path]["?"querystring]["#"fragment]
23739
23740    // extract fragment
23741    pos = string.indexOf('#');
23742    if (pos > -1) {
23743      // escaping?
23744      parts.fragment = string.substring(pos + 1) || null;
23745      string = string.substring(0, pos);
23746    }
23747
23748    // extract query
23749    pos = string.indexOf('?');
23750    if (pos > -1) {
23751      // escaping?
23752      parts.query = string.substring(pos + 1) || null;
23753      string = string.substring(0, pos);
23754    }
23755
23756    // extract protocol
23757    if (string.substring(0, 2) === '//') {
23758      // relative-scheme
23759      parts.protocol = null;
23760      string = string.substring(2);
23761      // extract "user:pass@host:port"
23762      string = URI.parseAuthority(string, parts);
23763    } else {
23764      pos = string.indexOf(':');
23765      if (pos > -1) {
23766        parts.protocol = string.substring(0, pos) || null;
23767        if (parts.protocol && !parts.protocol.match(URI.protocol_expression)) {
23768          // : may be within the path
23769          parts.protocol = undefined;
23770        } else if (string.substring(pos + 1, pos + 3) === '//') {
23771          string = string.substring(pos + 3);
23772
23773          // extract "user:pass@host:port"
23774          string = URI.parseAuthority(string, parts);
23775        } else {
23776          string = string.substring(pos + 1);
23777          parts.urn = true;
23778        }
23779      }
23780    }
23781
23782    // what's left must be the path
23783    parts.path = string;
23784
23785    // and we're done
23786    return parts;
23787  };
23788  URI.parseHost = function(string, parts) {
23789    // extract host:port
23790    var pos = string.indexOf('/');
23791    var bracketPos;
23792    var t;
23793
23794    if (pos === -1) {
23795      pos = string.length;
23796    }
23797
23798    if (string.charAt(0) === '[') {
23799      // IPv6 host - http://tools.ietf.org/html/draft-ietf-6man-text-addr-representation-04#section-6
23800      // I claim most client software breaks on IPv6 anyways. To simplify things, URI only accepts
23801      // IPv6+port in the format [2001:db8::1]:80 (for the time being)
23802      bracketPos = string.indexOf(']');
23803      parts.hostname = string.substring(1, bracketPos) || null;
23804      parts.port = string.substring(bracketPos + 2, pos) || null;
23805      if (parts.port === '/') {
23806        parts.port = null;
23807      }
23808    } else {
23809      var firstColon = string.indexOf(':');
23810      var firstSlash = string.indexOf('/');
23811      var nextColon = string.indexOf(':', firstColon + 1);
23812      if (nextColon !== -1 && (firstSlash === -1 || nextColon < firstSlash)) {
23813        // IPv6 host contains multiple colons - but no port
23814        // this notation is actually not allowed by RFC 3986, but we're a liberal parser
23815        parts.hostname = string.substring(0, pos) || null;
23816        parts.port = null;
23817      } else {
23818        t = string.substring(0, pos).split(':');
23819        parts.hostname = t[0] || null;
23820        parts.port = t[1] || null;
23821      }
23822    }
23823
23824    if (parts.hostname && string.substring(pos).charAt(0) !== '/') {
23825      pos++;
23826      string = '/' + string;
23827    }
23828
23829    return string.substring(pos) || '/';
23830  };
23831  URI.parseAuthority = function(string, parts) {
23832    string = URI.parseUserinfo(string, parts);
23833    return URI.parseHost(string, parts);
23834  };
23835  URI.parseUserinfo = function(string, parts) {
23836    // extract username:password
23837    var firstSlash = string.indexOf('/');
23838    var pos = string.lastIndexOf('@', firstSlash > -1 ? firstSlash : string.length - 1);
23839    var t;
23840
23841    // authority@ must come before /path
23842    if (pos > -1 && (firstSlash === -1 || pos < firstSlash)) {
23843      t = string.substring(0, pos).split(':');
23844      parts.username = t[0] ? URI.decode(t[0]) : null;
23845      t.shift();
23846      parts.password = t[0] ? URI.decode(t.join(':')) : null;
23847      string = string.substring(pos + 1);
23848    } else {
23849      parts.username = null;
23850      parts.password = null;
23851    }
23852
23853    return string;
23854  };
23855  URI.parseQuery = function(string, escapeQuerySpace) {
23856    if (!string) {
23857      return {};
23858    }
23859
23860    // throw out the funky business - "?"[name"="value"&"]+
23861    string = string.replace(/&+/g, '&').replace(/^\?*&*|&+$/g, '');
23862
23863    if (!string) {
23864      return {};
23865    }
23866
23867    var items = {};
23868    var splits = string.split('&');
23869    var length = splits.length;
23870    var v, name, value;
23871
23872    for (var i = 0; i < length; i++) {
23873      v = splits[i].split('=');
23874      name = URI.decodeQuery(v.shift(), escapeQuerySpace);
23875      // no "=" is null according to http://dvcs.w3.org/hg/url/raw-file/tip/Overview.html#collect-url-parameters
23876      value = v.length ? URI.decodeQuery(v.join('='), escapeQuerySpace) : null;
23877
23878      if (hasOwn.call(items, name)) {
23879        if (typeof items[name] === 'string') {
23880          items[name] = [items[name]];
23881        }
23882
23883        items[name].push(value);
23884      } else {
23885        items[name] = value;
23886      }
23887    }
23888
23889    return items;
23890  };
23891
23892  URI.build = function(parts) {
23893    var t = '';
23894
23895    if (parts.protocol) {
23896      t += parts.protocol + ':';
23897    }
23898
23899    if (!parts.urn && (t || parts.hostname)) {
23900      t += '//';
23901    }
23902
23903    t += (URI.buildAuthority(parts) || '');
23904
23905    if (typeof parts.path === 'string') {
23906      if (parts.path.charAt(0) !== '/' && typeof parts.hostname === 'string') {
23907        t += '/';
23908      }
23909
23910      t += parts.path;
23911    }
23912
23913    if (typeof parts.query === 'string' && parts.query) {
23914      t += '?' + parts.query;
23915    }
23916
23917    if (typeof parts.fragment === 'string' && parts.fragment) {
23918      t += '#' + parts.fragment;
23919    }
23920    return t;
23921  };
23922  URI.buildHost = function(parts) {
23923    var t = '';
23924
23925    if (!parts.hostname) {
23926      return '';
23927    } else if (URI.ip6_expression.test(parts.hostname)) {
23928      t += '[' + parts.hostname + ']';
23929    } else {
23930      t += parts.hostname;
23931    }
23932
23933    if (parts.port) {
23934      t += ':' + parts.port;
23935    }
23936
23937    return t;
23938  };
23939  URI.buildAuthority = function(parts) {
23940    return URI.buildUserinfo(parts) + URI.buildHost(parts);
23941  };
23942  URI.buildUserinfo = function(parts) {
23943    var t = '';
23944
23945    if (parts.username) {
23946      t += URI.encode(parts.username);
23947
23948      if (parts.password) {
23949        t += ':' + URI.encode(parts.password);
23950      }
23951
23952      t += '@';
23953    }
23954
23955    return t;
23956  };
23957  URI.buildQuery = function(data, duplicateQueryParameters, escapeQuerySpace) {
23958    // according to http://tools.ietf.org/html/rfc3986 or http://labs.apache.org/webarch/uri/rfc/rfc3986.html
23959    // being »-._~!$&'()*+,;=:@/?« %HEX and alnum are allowed
23960    // the RFC explicitly states ?/foo being a valid use case, no mention of parameter syntax!
23961    // URI.js treats the query string as being application/x-www-form-urlencoded
23962    // see http://www.w3.org/TR/REC-html40/interact/forms.html#form-content-type
23963
23964    var t = '';
23965    var unique, key, i, length;
23966    for (key in data) {
23967      if (hasOwn.call(data, key) && key) {
23968        if (isArray(data[key])) {
23969          unique = {};
23970          for (i = 0, length = data[key].length; i < length; i++) {
23971            if (data[key][i] !== undefined && unique[data[key][i] + ''] === undefined) {
23972              t += '&' + URI.buildQueryParameter(key, data[key][i], escapeQuerySpace);
23973              if (duplicateQueryParameters !== true) {
23974                unique[data[key][i] + ''] = true;
23975              }
23976            }
23977          }
23978        } else if (data[key] !== undefined) {
23979          t += '&' + URI.buildQueryParameter(key, data[key], escapeQuerySpace);
23980        }
23981      }
23982    }
23983
23984    return t.substring(1);
23985  };
23986  URI.buildQueryParameter = function(name, value, escapeQuerySpace) {
23987    // http://www.w3.org/TR/REC-html40/interact/forms.html#form-content-type -- application/x-www-form-urlencoded
23988    // don't append "=" for null values, according to http://dvcs.w3.org/hg/url/raw-file/tip/Overview.html#url-parameter-serialization
23989    return URI.encodeQuery(name, escapeQuerySpace) + (value !== null ? '=' + URI.encodeQuery(value, escapeQuerySpace) : '');
23990  };
23991
23992  URI.addQuery = function(data, name, value) {
23993    if (typeof name === 'object') {
23994      for (var key in name) {
23995        if (hasOwn.call(name, key)) {
23996          URI.addQuery(data, key, name[key]);
23997        }
23998      }
23999    } else if (typeof name === 'string') {
24000      if (data[name] === undefined) {
24001        data[name] = value;
24002        return;
24003      } else if (typeof data[name] === 'string') {
24004        data[name] = [data[name]];
24005      }
24006
24007      if (!isArray(value)) {
24008        value = [value];
24009      }
24010
24011      data[name] = (data[name] || []).concat(value);
24012    } else {
24013      throw new TypeError('URI.addQuery() accepts an object, string as the name parameter');
24014    }
24015  };
24016  URI.removeQuery = function(data, name, value) {
24017    var i, length, key;
24018
24019    if (isArray(name)) {
24020      for (i = 0, length = name.length; i < length; i++) {
24021        data[name[i]] = undefined;
24022      }
24023    } else if (typeof name === 'object') {
24024      for (key in name) {
24025        if (hasOwn.call(name, key)) {
24026          URI.removeQuery(data, key, name[key]);
24027        }
24028      }
24029    } else if (typeof name === 'string') {
24030      if (value !== undefined) {
24031        if (data[name] === value) {
24032          data[name] = undefined;
24033        } else if (isArray(data[name])) {
24034          data[name] = filterArrayValues(data[name], value);
24035        }
24036      } else {
24037        data[name] = undefined;
24038      }
24039    } else {
24040      throw new TypeError('URI.removeQuery() accepts an object, string as the first parameter');
24041    }
24042  };
24043  URI.hasQuery = function(data, name, value, withinArray) {
24044    if (typeof name === 'object') {
24045      for (var key in name) {
24046        if (hasOwn.call(name, key)) {
24047          if (!URI.hasQuery(data, key, name[key])) {
24048            return false;
24049          }
24050        }
24051      }
24052
24053      return true;
24054    } else if (typeof name !== 'string') {
24055      throw new TypeError('URI.hasQuery() accepts an object, string as the name parameter');
24056    }
24057
24058    switch (getType(value)) {
24059      case 'Undefined':
24060        // true if exists (but may be empty)
24061        return name in data; // data[name] !== undefined;
24062
24063      case 'Boolean':
24064        // true if exists and non-empty
24065        var _booly = Boolean(isArray(data[name]) ? data[name].length : data[name]);
24066        return value === _booly;
24067
24068      case 'Function':
24069        // allow complex comparison
24070        return !!value(data[name], name, data);
24071
24072      case 'Array':
24073        if (!isArray(data[name])) {
24074          return false;
24075        }
24076
24077        var op = withinArray ? arrayContains : arraysEqual;
24078        return op(data[name], value);
24079
24080      case 'RegExp':
24081        if (!isArray(data[name])) {
24082          return Boolean(data[name] && data[name].match(value));
24083        }
24084
24085        if (!withinArray) {
24086          return false;
24087        }
24088
24089        return arrayContains(data[name], value);
24090
24091      case 'Number':
24092        value = String(value);
24093        /* falls through */
24094      case 'String':
24095        if (!isArray(data[name])) {
24096          return data[name] === value;
24097        }
24098
24099        if (!withinArray) {
24100          return false;
24101        }
24102
24103        return arrayContains(data[name], value);
24104
24105      default:
24106        throw new TypeError('URI.hasQuery() accepts undefined, boolean, string, number, RegExp, Function as the value parameter');
24107    }
24108  };
24109
24110
24111  URI.commonPath = function(one, two) {
24112    var length = Math.min(one.length, two.length);
24113    var pos;
24114
24115    // find first non-matching character
24116    for (pos = 0; pos < length; pos++) {
24117      if (one.charAt(pos) !== two.charAt(pos)) {
24118        pos--;
24119        break;
24120      }
24121    }
24122
24123    if (pos < 1) {
24124      return one.charAt(0) === two.charAt(0) && one.charAt(0) === '/' ? '/' : '';
24125    }
24126
24127    // revert to last /
24128    if (one.charAt(pos) !== '/' || two.charAt(pos) !== '/') {
24129      pos = one.substring(0, pos).lastIndexOf('/');
24130    }
24131
24132    return one.substring(0, pos + 1);
24133  };
24134
24135  URI.withinString = function(string, callback, options) {
24136    options || (options = {});
24137    var _start = options.start || URI.findUri.start;
24138    var _end = options.end || URI.findUri.end;
24139    var _trim = options.trim || URI.findUri.trim;
24140    var _attributeOpen = /[a-z0-9-]=["']?$/i;
24141
24142    _start.lastIndex = 0;
24143    while (true) {
24144      var match = _start.exec(string);
24145      if (!match) {
24146        break;
24147      }
24148
24149      var start = match.index;
24150      if (options.ignoreHtml) {
24151        // attribut(e=["']?$)
24152        var attributeOpen = string.slice(Math.max(start - 3, 0), start);
24153        if (attributeOpen && _attributeOpen.test(attributeOpen)) {
24154          continue;
24155        }
24156      }
24157
24158      var end = start + string.slice(start).search(_end);
24159      var slice = string.slice(start, end).replace(_trim, '');
24160      if (options.ignore && options.ignore.test(slice)) {
24161        continue;
24162      }
24163
24164      end = start + slice.length;
24165      var result = callback(slice, start, end, string);
24166      string = string.slice(0, start) + result + string.slice(end);
24167      _start.lastIndex = start + result.length;
24168    }
24169
24170    _start.lastIndex = 0;
24171    return string;
24172  };
24173
24174  URI.ensureValidHostname = function(v) {
24175    // Theoretically URIs allow percent-encoding in Hostnames (according to RFC 3986)
24176    // they are not part of DNS and therefore ignored by URI.js
24177
24178    if (v.match(URI.invalid_hostname_characters)) {
24179      // test punycode
24180      if (!punycode) {
24181        throw new TypeError('Hostname "' + v + '" contains characters other than [A-Z0-9.-] and Punycode.js is not available');
24182      }
24183
24184      if (punycode.toASCII(v).match(URI.invalid_hostname_characters)) {
24185        throw new TypeError('Hostname "' + v + '" contains characters other than [A-Z0-9.-]');
24186      }
24187    }
24188  };
24189
24190  // noConflict
24191  URI.noConflict = function(removeAll) {
24192    if (removeAll) {
24193      var unconflicted = {
24194        URI: this.noConflict()
24195      };
24196
24197      if (root.URITemplate && typeof root.URITemplate.noConflict === 'function') {
24198        unconflicted.URITemplate = root.URITemplate.noConflict();
24199      }
24200
24201      if (root.IPv6 && typeof root.IPv6.noConflict === 'function') {
24202        unconflicted.IPv6 = root.IPv6.noConflict();
24203      }
24204
24205      if (root.SecondLevelDomains && typeof root.SecondLevelDomains.noConflict === 'function') {
24206        unconflicted.SecondLevelDomains = root.SecondLevelDomains.noConflict();
24207      }
24208
24209      return unconflicted;
24210    } else if (root.URI === this) {
24211      root.URI = _URI;
24212    }
24213
24214    return this;
24215  };
24216
24217  p.build = function(deferBuild) {
24218    if (deferBuild === true) {
24219      this._deferred_build = true;
24220    } else if (deferBuild === undefined || this._deferred_build) {
24221      this._string = URI.build(this._parts);
24222      this._deferred_build = false;
24223    }
24224
24225    return this;
24226  };
24227
24228  p.clone = function() {
24229    return new URI(this);
24230  };
24231
24232  p.valueOf = p.toString = function() {
24233    return this.build(false)._string;
24234  };
24235
24236
24237  function generateSimpleAccessor(_part){
24238    return function(v, build) {
24239      if (v === undefined) {
24240        return this._parts[_part] || '';
24241      } else {
24242        this._parts[_part] = v || null;
24243        this.build(!build);
24244        return this;
24245      }
24246    };
24247  }
24248
24249  function generatePrefixAccessor(_part, _key){
24250    return function(v, build) {
24251      if (v === undefined) {
24252        return this._parts[_part] || '';
24253      } else {
24254        if (v !== null) {
24255          v = v + '';
24256          if (v.charAt(0) === _key) {
24257            v = v.substring(1);
24258          }
24259        }
24260
24261        this._parts[_part] = v;
24262        this.build(!build);
24263        return this;
24264      }
24265    };
24266  }
24267
24268  p.protocol = generateSimpleAccessor('protocol');
24269  p.username = generateSimpleAccessor('username');
24270  p.password = generateSimpleAccessor('password');
24271  p.hostname = generateSimpleAccessor('hostname');
24272  p.port = generateSimpleAccessor('port');
24273  p.query = generatePrefixAccessor('query', '?');
24274  p.fragment = generatePrefixAccessor('fragment', '#');
24275
24276  p.search = function(v, build) {
24277    var t = this.query(v, build);
24278    return typeof t === 'string' && t.length ? ('?' + t) : t;
24279  };
24280  p.hash = function(v, build) {
24281    var t = this.fragment(v, build);
24282    return typeof t === 'string' && t.length ? ('#' + t) : t;
24283  };
24284
24285  p.pathname = function(v, build) {
24286    if (v === undefined || v === true) {
24287      var res = this._parts.path || (this._parts.hostname ? '/' : '');
24288      return v ? (this._parts.urn ? URI.decodeUrnPath : URI.decodePath)(res) : res;
24289    } else {
24290      if (this._parts.urn) {
24291        this._parts.path = v ? URI.recodeUrnPath(v) : '';
24292      } else {
24293        this._parts.path = v ? URI.recodePath(v) : '/';
24294      }
24295      this.build(!build);
24296      return this;
24297    }
24298  };
24299  p.path = p.pathname;
24300  p.href = function(href, build) {
24301    var key;
24302
24303    if (href === undefined) {
24304      return this.toString();
24305    }
24306
24307    this._string = '';
24308    this._parts = URI._parts();
24309
24310    var _URI = href instanceof URI;
24311    var _object = typeof href === 'object' && (href.hostname || href.path || href.pathname);
24312    if (href.nodeName) {
24313      var attribute = URI.getDomAttribute(href);
24314      href = href[attribute] || '';
24315      _object = false;
24316    }
24317
24318    // window.location is reported to be an object, but it's not the sort
24319    // of object we're looking for:
24320    // * location.protocol ends with a colon
24321    // * location.query != object.search
24322    // * location.hash != object.fragment
24323    // simply serializing the unknown object should do the trick
24324    // (for location, not for everything...)
24325    if (!_URI && _object && href.pathname !== undefined) {
24326      href = href.toString();
24327    }
24328
24329    if (typeof href === 'string' || href instanceof String) {
24330      this._parts = URI.parse(String(href), this._parts);
24331    } else if (_URI || _object) {
24332      var src = _URI ? href._parts : href;
24333      for (key in src) {
24334        if (hasOwn.call(this._parts, key)) {
24335          this._parts[key] = src[key];
24336        }
24337      }
24338    } else {
24339      throw new TypeError('invalid input');
24340    }
24341
24342    this.build(!build);
24343    return this;
24344  };
24345
24346  // identification accessors
24347  p.is = function(what) {
24348    var ip = false;
24349    var ip4 = false;
24350    var ip6 = false;
24351    var name = false;
24352    var sld = false;
24353    var idn = false;
24354    var punycode = false;
24355    var relative = !this._parts.urn;
24356
24357    if (this._parts.hostname) {
24358      relative = false;
24359      ip4 = URI.ip4_expression.test(this._parts.hostname);
24360      ip6 = URI.ip6_expression.test(this._parts.hostname);
24361      ip = ip4 || ip6;
24362      name = !ip;
24363      sld = name && SLD && SLD.has(this._parts.hostname);
24364      idn = name && URI.idn_expression.test(this._parts.hostname);
24365      punycode = name && URI.punycode_expression.test(this._parts.hostname);
24366    }
24367
24368    switch (what.toLowerCase()) {
24369      case 'relative':
24370        return relative;
24371
24372      case 'absolute':
24373        return !relative;
24374
24375      // hostname identification
24376      case 'domain':
24377      case 'name':
24378        return name;
24379
24380      case 'sld':
24381        return sld;
24382
24383      case 'ip':
24384        return ip;
24385
24386      case 'ip4':
24387      case 'ipv4':
24388      case 'inet4':
24389        return ip4;
24390
24391      case 'ip6':
24392      case 'ipv6':
24393      case 'inet6':
24394        return ip6;
24395
24396      case 'idn':
24397        return idn;
24398
24399      case 'url':
24400        return !this._parts.urn;
24401
24402      case 'urn':
24403        return !!this._parts.urn;
24404
24405      case 'punycode':
24406        return punycode;
24407    }
24408
24409    return null;
24410  };
24411
24412  // component specific input validation
24413  var _protocol = p.protocol;
24414  var _port = p.port;
24415  var _hostname = p.hostname;
24416
24417  p.protocol = function(v, build) {
24418    if (v !== undefined) {
24419      if (v) {
24420        // accept trailing ://
24421        v = v.replace(/:(\/\/)?$/, '');
24422
24423        if (!v.match(URI.protocol_expression)) {
24424          throw new TypeError('Protocol "' + v + '" contains characters other than [A-Z0-9.+-] or doesn\'t start with [A-Z]');
24425        }
24426      }
24427    }
24428    return _protocol.call(this, v, build);
24429  };
24430  p.scheme = p.protocol;
24431  p.port = function(v, build) {
24432    if (this._parts.urn) {
24433      return v === undefined ? '' : this;
24434    }
24435
24436    if (v !== undefined) {
24437      if (v === 0) {
24438        v = null;
24439      }
24440
24441      if (v) {
24442        v += '';
24443        if (v.charAt(0) === ':') {
24444          v = v.substring(1);
24445        }
24446
24447        if (v.match(/[^0-9]/)) {
24448          throw new TypeError('Port "' + v + '" contains characters other than [0-9]');
24449        }
24450      }
24451    }
24452    return _port.call(this, v, build);
24453  };
24454  p.hostname = function(v, build) {
24455    if (this._parts.urn) {
24456      return v === undefined ? '' : this;
24457    }
24458
24459    if (v !== undefined) {
24460      var x = {};
24461      URI.parseHost(v, x);
24462      v = x.hostname;
24463    }
24464    return _hostname.call(this, v, build);
24465  };
24466
24467  // compound accessors
24468  p.host = function(v, build) {
24469    if (this._parts.urn) {
24470      return v === undefined ? '' : this;
24471    }
24472
24473    if (v === undefined) {
24474      return this._parts.hostname ? URI.buildHost(this._parts) : '';
24475    } else {
24476      URI.parseHost(v, this._parts);
24477      this.build(!build);
24478      return this;
24479    }
24480  };
24481  p.authority = function(v, build) {
24482    if (this._parts.urn) {
24483      return v === undefined ? '' : this;
24484    }
24485
24486    if (v === undefined) {
24487      return this._parts.hostname ? URI.buildAuthority(this._parts) : '';
24488    } else {
24489      URI.parseAuthority(v, this._parts);
24490      this.build(!build);
24491      return this;
24492    }
24493  };
24494  p.userinfo = function(v, build) {
24495    if (this._parts.urn) {
24496      return v === undefined ? '' : this;
24497    }
24498
24499    if (v === undefined) {
24500      if (!this._parts.username) {
24501        return '';
24502      }
24503
24504      var t = URI.buildUserinfo(this._parts);
24505      return t.substring(0, t.length -1);
24506    } else {
24507      if (v[v.length-1] !== '@') {
24508        v += '@';
24509      }
24510
24511      URI.parseUserinfo(v, this._parts);
24512      this.build(!build);
24513      return this;
24514    }
24515  };
24516  p.resource = function(v, build) {
24517    var parts;
24518
24519    if (v === undefined) {
24520      return this.path() + this.search() + this.hash();
24521    }
24522
24523    parts = URI.parse(v);
24524    this._parts.path = parts.path;
24525    this._parts.query = parts.query;
24526    this._parts.fragment = parts.fragment;
24527    this.build(!build);
24528    return this;
24529  };
24530
24531  // fraction accessors
24532  p.subdomain = function(v, build) {
24533    if (this._parts.urn) {
24534      return v === undefined ? '' : this;
24535    }
24536
24537    // convenience, return "www" from "www.example.org"
24538    if (v === undefined) {
24539      if (!this._parts.hostname || this.is('IP')) {
24540        return '';
24541      }
24542
24543      // grab domain and add another segment
24544      var end = this._parts.hostname.length - this.domain().length - 1;
24545      return this._parts.hostname.substring(0, end) || '';
24546    } else {
24547      var e = this._parts.hostname.length - this.domain().length;
24548      var sub = this._parts.hostname.substring(0, e);
24549      var replace = new RegExp('^' + escapeRegEx(sub));
24550
24551      if (v && v.charAt(v.length - 1) !== '.') {
24552        v += '.';
24553      }
24554
24555      if (v) {
24556        URI.ensureValidHostname(v);
24557      }
24558
24559      this._parts.hostname = this._parts.hostname.replace(replace, v);
24560      this.build(!build);
24561      return this;
24562    }
24563  };
24564  p.domain = function(v, build) {
24565    if (this._parts.urn) {
24566      return v === undefined ? '' : this;
24567    }
24568
24569    if (typeof v === 'boolean') {
24570      build = v;
24571      v = undefined;
24572    }
24573
24574    // convenience, return "example.org" from "www.example.org"
24575    if (v === undefined) {
24576      if (!this._parts.hostname || this.is('IP')) {
24577        return '';
24578      }
24579
24580      // if hostname consists of 1 or 2 segments, it must be the domain
24581      var t = this._parts.hostname.match(/\./g);
24582      if (t && t.length < 2) {
24583        return this._parts.hostname;
24584      }
24585
24586      // grab tld and add another segment
24587      var end = this._parts.hostname.length - this.tld(build).length - 1;
24588      end = this._parts.hostname.lastIndexOf('.', end -1) + 1;
24589      return this._parts.hostname.substring(end) || '';
24590    } else {
24591      if (!v) {
24592        throw new TypeError('cannot set domain empty');
24593      }
24594
24595      URI.ensureValidHostname(v);
24596
24597      if (!this._parts.hostname || this.is('IP')) {
24598        this._parts.hostname = v;
24599      } else {
24600        var replace = new RegExp(escapeRegEx(this.domain()) + '$');
24601        this._parts.hostname = this._parts.hostname.replace(replace, v);
24602      }
24603
24604      this.build(!build);
24605      return this;
24606    }
24607  };
24608  p.tld = function(v, build) {
24609    if (this._parts.urn) {
24610      return v === undefined ? '' : this;
24611    }
24612
24613    if (typeof v === 'boolean') {
24614      build = v;
24615      v = undefined;
24616    }
24617
24618    // return "org" from "www.example.org"
24619    if (v === undefined) {
24620      if (!this._parts.hostname || this.is('IP')) {
24621        return '';
24622      }
24623
24624      var pos = this._parts.hostname.lastIndexOf('.');
24625      var tld = this._parts.hostname.substring(pos + 1);
24626
24627      if (build !== true && SLD && SLD.list[tld.toLowerCase()]) {
24628        return SLD.get(this._parts.hostname) || tld;
24629      }
24630
24631      return tld;
24632    } else {
24633      var replace;
24634
24635      if (!v) {
24636        throw new TypeError('cannot set TLD empty');
24637      } else if (v.match(/[^a-zA-Z0-9-]/)) {
24638        if (SLD && SLD.is(v)) {
24639          replace = new RegExp(escapeRegEx(this.tld()) + '$');
24640          this._parts.hostname = this._parts.hostname.replace(replace, v);
24641        } else {
24642          throw new TypeError('TLD "' + v + '" contains characters other than [A-Z0-9]');
24643        }
24644      } else if (!this._parts.hostname || this.is('IP')) {
24645        throw new ReferenceError('cannot set TLD on non-domain host');
24646      } else {
24647        replace = new RegExp(escapeRegEx(this.tld()) + '$');
24648        this._parts.hostname = this._parts.hostname.replace(replace, v);
24649      }
24650
24651      this.build(!build);
24652      return this;
24653    }
24654  };
24655  p.directory = function(v, build) {
24656    if (this._parts.urn) {
24657      return v === undefined ? '' : this;
24658    }
24659
24660    if (v === undefined || v === true) {
24661      if (!this._parts.path && !this._parts.hostname) {
24662        return '';
24663      }
24664
24665      if (this._parts.path === '/') {
24666        return '/';
24667      }
24668
24669      var end = this._parts.path.length - this.filename().length - 1;
24670      var res = this._parts.path.substring(0, end) || (this._parts.hostname ? '/' : '');
24671
24672      return v ? URI.decodePath(res) : res;
24673
24674    } else {
24675      var e = this._parts.path.length - this.filename().length;
24676      var directory = this._parts.path.substring(0, e);
24677      var replace = new RegExp('^' + escapeRegEx(directory));
24678
24679      // fully qualifier directories begin with a slash
24680      if (!this.is('relative')) {
24681        if (!v) {
24682          v = '/';
24683        }
24684
24685        if (v.charAt(0) !== '/') {
24686          v = '/' + v;
24687        }
24688      }
24689
24690      // directories always end with a slash
24691      if (v && v.charAt(v.length - 1) !== '/') {
24692        v += '/';
24693      }
24694
24695      v = URI.recodePath(v);
24696      this._parts.path = this._parts.path.replace(replace, v);
24697      this.build(!build);
24698      return this;
24699    }
24700  };
24701  p.filename = function(v, build) {
24702    if (this._parts.urn) {
24703      return v === undefined ? '' : this;
24704    }
24705
24706    if (v === undefined || v === true) {
24707      if (!this._parts.path || this._parts.path === '/') {
24708        return '';
24709      }
24710
24711      var pos = this._parts.path.lastIndexOf('/');
24712      var res = this._parts.path.substring(pos+1);
24713
24714      return v ? URI.decodePathSegment(res) : res;
24715    } else {
24716      var mutatedDirectory = false;
24717
24718      if (v.charAt(0) === '/') {
24719        v = v.substring(1);
24720      }
24721
24722      if (v.match(/\.?\//)) {
24723        mutatedDirectory = true;
24724      }
24725
24726      var replace = new RegExp(escapeRegEx(this.filename()) + '$');
24727      v = URI.recodePath(v);
24728      this._parts.path = this._parts.path.replace(replace, v);
24729
24730      if (mutatedDirectory) {
24731        this.normalizePath(build);
24732      } else {
24733        this.build(!build);
24734      }
24735
24736      return this;
24737    }
24738  };
24739  p.suffix = function(v, build) {
24740    if (this._parts.urn) {
24741      return v === undefined ? '' : this;
24742    }
24743
24744    if (v === undefined || v === true) {
24745      if (!this._parts.path || this._parts.path === '/') {
24746        return '';
24747      }
24748
24749      var filename = this.filename();
24750      var pos = filename.lastIndexOf('.');
24751      var s, res;
24752
24753      if (pos === -1) {
24754        return '';
24755      }
24756
24757      // suffix may only contain alnum characters (yup, I made this up.)
24758      s = filename.substring(pos+1);
24759      res = (/^[a-z0-9%]+$/i).test(s) ? s : '';
24760      return v ? URI.decodePathSegment(res) : res;
24761    } else {
24762      if (v.charAt(0) === '.') {
24763        v = v.substring(1);
24764      }
24765
24766      var suffix = this.suffix();
24767      var replace;
24768
24769      if (!suffix) {
24770        if (!v) {
24771          return this;
24772        }
24773
24774        this._parts.path += '.' + URI.recodePath(v);
24775      } else if (!v) {
24776        replace = new RegExp(escapeRegEx('.' + suffix) + '$');
24777      } else {
24778        replace = new RegExp(escapeRegEx(suffix) + '$');
24779      }
24780
24781      if (replace) {
24782        v = URI.recodePath(v);
24783        this._parts.path = this._parts.path.replace(replace, v);
24784      }
24785
24786      this.build(!build);
24787      return this;
24788    }
24789  };
24790  p.segment = function(segment, v, build) {
24791    var separator = this._parts.urn ? ':' : '/';
24792    var path = this.path();
24793    var absolute = path.substring(0, 1) === '/';
24794    var segments = path.split(separator);
24795
24796    if (segment !== undefined && typeof segment !== 'number') {
24797      build = v;
24798      v = segment;
24799      segment = undefined;
24800    }
24801
24802    if (segment !== undefined && typeof segment !== 'number') {
24803      throw new Error('Bad segment "' + segment + '", must be 0-based integer');
24804    }
24805
24806    if (absolute) {
24807      segments.shift();
24808    }
24809
24810    if (segment < 0) {
24811      // allow negative indexes to address from the end
24812      segment = Math.max(segments.length + segment, 0);
24813    }
24814
24815    if (v === undefined) {
24816      /*jshint laxbreak: true */
24817      return segment === undefined
24818        ? segments
24819        : segments[segment];
24820      /*jshint laxbreak: false */
24821    } else if (segment === null || segments[segment] === undefined) {
24822      if (isArray(v)) {
24823        segments = [];
24824        // collapse empty elements within array
24825        for (var i=0, l=v.length; i < l; i++) {
24826          if (!v[i].length && (!segments.length || !segments[segments.length -1].length)) {
24827            continue;
24828          }
24829
24830          if (segments.length && !segments[segments.length -1].length) {
24831            segments.pop();
24832          }
24833
24834          segments.push(v[i]);
24835        }
24836      } else if (v || typeof v === 'string') {
24837        if (segments[segments.length -1] === '') {
24838          // empty trailing elements have to be overwritten
24839          // to prevent results such as /foo//bar
24840          segments[segments.length -1] = v;
24841        } else {
24842          segments.push(v);
24843        }
24844      }
24845    } else {
24846      if (v) {
24847        segments[segment] = v;
24848      } else {
24849        segments.splice(segment, 1);
24850      }
24851    }
24852
24853    if (absolute) {
24854      segments.unshift('');
24855    }
24856
24857    return this.path(segments.join(separator), build);
24858  };
24859  p.segmentCoded = function(segment, v, build) {
24860    var segments, i, l;
24861
24862    if (typeof segment !== 'number') {
24863      build = v;
24864      v = segment;
24865      segment = undefined;
24866    }
24867
24868    if (v === undefined) {
24869      segments = this.segment(segment, v, build);
24870      if (!isArray(segments)) {
24871        segments = segments !== undefined ? URI.decode(segments) : undefined;
24872      } else {
24873        for (i = 0, l = segments.length; i < l; i++) {
24874          segments[i] = URI.decode(segments[i]);
24875        }
24876      }
24877
24878      return segments;
24879    }
24880
24881    if (!isArray(v)) {
24882      v = (typeof v === 'string' || v instanceof String) ? URI.encode(v) : v;
24883    } else {
24884      for (i = 0, l = v.length; i < l; i++) {
24885        v[i] = URI.decode(v[i]);
24886      }
24887    }
24888
24889    return this.segment(segment, v, build);
24890  };
24891
24892  // mutating query string
24893  var q = p.query;
24894  p.query = function(v, build) {
24895    if (v === true) {
24896      return URI.parseQuery(this._parts.query, this._parts.escapeQuerySpace);
24897    } else if (typeof v === 'function') {
24898      var data = URI.parseQuery(this._parts.query, this._parts.escapeQuerySpace);
24899      var result = v.call(this, data);
24900      this._parts.query = URI.buildQuery(result || data, this._parts.duplicateQueryParameters, this._parts.escapeQuerySpace);
24901      this.build(!build);
24902      return this;
24903    } else if (v !== undefined && typeof v !== 'string') {
24904      this._parts.query = URI.buildQuery(v, this._parts.duplicateQueryParameters, this._parts.escapeQuerySpace);
24905      this.build(!build);
24906      return this;
24907    } else {
24908      return q.call(this, v, build);
24909    }
24910  };
24911  p.setQuery = function(name, value, build) {
24912    var data = URI.parseQuery(this._parts.query, this._parts.escapeQuerySpace);
24913
24914    if (typeof name === 'string' || name instanceof String) {
24915      data[name] = value !== undefined ? value : null;
24916    } else if (typeof name === 'object') {
24917      for (var key in name) {
24918        if (hasOwn.call(name, key)) {
24919          data[key] = name[key];
24920        }
24921      }
24922    } else {
24923      throw new TypeError('URI.addQuery() accepts an object, string as the name parameter');
24924    }
24925
24926    this._parts.query = URI.buildQuery(data, this._parts.duplicateQueryParameters, this._parts.escapeQuerySpace);
24927    if (typeof name !== 'string') {
24928      build = value;
24929    }
24930
24931    this.build(!build);
24932    return this;
24933  };
24934  p.addQuery = function(name, value, build) {
24935    var data = URI.parseQuery(this._parts.query, this._parts.escapeQuerySpace);
24936    URI.addQuery(data, name, value === undefined ? null : value);
24937    this._parts.query = URI.buildQuery(data, this._parts.duplicateQueryParameters, this._parts.escapeQuerySpace);
24938    if (typeof name !== 'string') {
24939      build = value;
24940    }
24941
24942    this.build(!build);
24943    return this;
24944  };
24945  p.removeQuery = function(name, value, build) {
24946    var data = URI.parseQuery(this._parts.query, this._parts.escapeQuerySpace);
24947    URI.removeQuery(data, name, value);
24948    this._parts.query = URI.buildQuery(data, this._parts.duplicateQueryParameters, this._parts.escapeQuerySpace);
24949    if (typeof name !== 'string') {
24950      build = value;
24951    }
24952
24953    this.build(!build);
24954    return this;
24955  };
24956  p.hasQuery = function(name, value, withinArray) {
24957    var data = URI.parseQuery(this._parts.query, this._parts.escapeQuerySpace);
24958    return URI.hasQuery(data, name, value, withinArray);
24959  };
24960  p.setSearch = p.setQuery;
24961  p.addSearch = p.addQuery;
24962  p.removeSearch = p.removeQuery;
24963  p.hasSearch = p.hasQuery;
24964
24965  // sanitizing URLs
24966  p.normalize = function() {
24967    if (this._parts.urn) {
24968      return this
24969        .normalizeProtocol(false)
24970        .normalizePath(false)
24971        .normalizeQuery(false)
24972        .normalizeFragment(false)
24973        .build();
24974    }
24975
24976    return this
24977      .normalizeProtocol(false)
24978      .normalizeHostname(false)
24979      .normalizePort(false)
24980      .normalizePath(false)
24981      .normalizeQuery(false)
24982      .normalizeFragment(false)
24983      .build();
24984  };
24985  p.normalizeProtocol = function(build) {
24986    if (typeof this._parts.protocol === 'string') {
24987      this._parts.protocol = this._parts.protocol.toLowerCase();
24988      this.build(!build);
24989    }
24990
24991    return this;
24992  };
24993  p.normalizeHostname = function(build) {
24994    if (this._parts.hostname) {
24995      // If this is an international domain name, or if it was an IDN before it was punycoded
24996      if (this.is('IDN') || this.is('punycode')) {
24997        this._parts.hostname = URI.recodeHostname(this._parts.hostname);
24998      } else if (this.is('IPv6') && IPv6) {
24999        this._parts.hostname = IPv6.best(this._parts.hostname);
25000      }
25001
25002      this._parts.hostname = this._parts.hostname.toLowerCase();
25003      this.build(!build);
25004    }
25005
25006    return this;
25007  };
25008  p.normalizePort = function(build) {
25009    // remove port of it's the protocol's default
25010    if (typeof this._parts.protocol === 'string' && this._parts.port === URI.defaultPorts[this._parts.protocol]) {
25011      this._parts.port = null;
25012      this.build(!build);
25013    }
25014
25015    return this;
25016  };
25017  p.normalizePath = function(build) {
25018    var _path = this._parts.path;
25019    if (!_path) {
25020      return this;
25021    }
25022
25023    if (this._parts.urn) {
25024      this._parts.path = URI.recodeUrnPath(this._parts.path);
25025      this.build(!build);
25026      return this;
25027    }
25028
25029    if (this._parts.path === '/') {
25030      return this;
25031    }
25032
25033    var _was_relative;
25034    var _leadingParents = '';
25035    var _parent, _pos;
25036
25037    // handle relative paths
25038    if (_path.charAt(0) !== '/') {
25039      _was_relative = true;
25040      _path = '/' + _path;
25041    }
25042
25043    // resolve simples
25044    _path = _path
25045      .replace(/(\/(\.\/)+)|(\/\.$)/g, '/')
25046      .replace(/\/{2,}/g, '/');
25047
25048    // remember leading parents
25049    if (_was_relative) {
25050      _leadingParents = _path.substring(1).match(/^(\.\.\/)+/) || '';
25051      if (_leadingParents) {
25052        _leadingParents = _leadingParents[0];
25053      }
25054    }
25055
25056    // resolve parents
25057    while (true) {
25058      _parent = _path.indexOf('/..');
25059      if (_parent === -1) {
25060        // no more ../ to resolve
25061        break;
25062      } else if (_parent === 0) {
25063        // top level cannot be relative, skip it
25064        _path = _path.substring(3);
25065        continue;
25066      }
25067
25068      _pos = _path.substring(0, _parent).lastIndexOf('/');
25069      if (_pos === -1) {
25070        _pos = _parent;
25071      }
25072      _path = _path.substring(0, _pos) + _path.substring(_parent + 3);
25073    }
25074
25075    // revert to relative
25076    if (_was_relative && this.is('relative')) {
25077      _path = _leadingParents + _path.substring(1);
25078    }
25079
25080    _path = URI.recodePath(_path);
25081    this._parts.path = _path;
25082    this.build(!build);
25083    return this;
25084  };
25085  p.normalizePathname = p.normalizePath;
25086  p.normalizeQuery = function(build) {
25087    if (typeof this._parts.query === 'string') {
25088      if (!this._parts.query.length) {
25089        this._parts.query = null;
25090      } else {
25091        this.query(URI.parseQuery(this._parts.query, this._parts.escapeQuerySpace));
25092      }
25093
25094      this.build(!build);
25095    }
25096
25097    return this;
25098  };
25099  p.normalizeFragment = function(build) {
25100    if (!this._parts.fragment) {
25101      this._parts.fragment = null;
25102      this.build(!build);
25103    }
25104
25105    return this;
25106  };
25107  p.normalizeSearch = p.normalizeQuery;
25108  p.normalizeHash = p.normalizeFragment;
25109
25110  function _generateNormalizer(hostnameRecoder, encoder, decoder) {
25111    return function() {
25112      var r = URI.recodeHostname
25113      var e = URI.encode;
25114      var d = URI.decode;
25115
25116      URI.encode = encoder;
25117      URI.decode = decoder;
25118      try {
25119        this.normalize();
25120      } finally {
25121        URI.recodeHostname = r;
25122        URI.encode = e;
25123        URI.decode = d;
25124      }
25125      return this;
25126    };
25127  }
25128
25129  // expect unicode input, iso8859 output
25130  p.iso8859 = _generateNormalizer(URI._defaultRecodeHostname, escape, decodeURIComponent);
25131  // expect iso8859 input, unicode output
25132  p.unicode = _generateNormalizer(URI._defaultRecodeHostname, strictEncodeURIComponent, unescape);
25133  // expect unicode input, IRI output
25134  p.iri = _generateNormalizer(recodeIRIHostname, encodeIRIComponent, decodeURIComponent);
25135
25136  p.readable = function() {
25137    var uri = this.clone();
25138    // removing username, password, because they shouldn't be displayed according to RFC 3986
25139    uri.username('').password('').normalize();
25140    var t = '';
25141    if (uri._parts.protocol) {
25142      t += uri._parts.protocol + (uri._parts.urn ? ':' : '://');
25143    }
25144
25145    if (uri._parts.hostname) {
25146      if (uri.is('punycode') && punycode) {
25147        t += punycode.toUnicode(uri._parts.hostname);
25148        if (uri._parts.port) {
25149          t += ':' + uri._parts.port;
25150        }
25151      } else {
25152        t += uri.host();
25153      }
25154    }
25155
25156    if (uri._parts.hostname && uri._parts.path && uri._parts.path.charAt(0) !== '/') {
25157      t += '/';
25158    }
25159
25160    t += uri.path(true);
25161    if (uri._parts.query) {
25162      var q = '';
25163      for (var i = 0, qp = uri._parts.query.split('&'), l = qp.length; i < l; i++) {
25164        var kv = (qp[i] || '').split('=');
25165        q += '&' + URI.decodeQuery(kv[0], this._parts.escapeQuerySpace)
25166          .replace(/&/g, '%26');
25167
25168        if (kv[1] !== undefined) {
25169          q += '=' + URI.decodeQuery(kv[1], this._parts.escapeQuerySpace)
25170            .replace(/&/g, '%26');
25171        }
25172      }
25173      t += '?' + q.substring(1);
25174    }
25175
25176    t += URI.decodeQuery(uri.hash(), true);
25177    return t;
25178  };
25179
25180  // resolving relative and absolute URLs
25181  p.absoluteTo = function(base) {
25182    var resolved = this.clone();
25183    var properties = ['protocol', 'username', 'password', 'hostname', 'port'];
25184    var basedir, i, p;
25185
25186    if (this._parts.urn) {
25187      throw new Error('URNs do not have any generally defined hierarchical components');
25188    }
25189
25190    if (!(base instanceof URI)) {
25191      base = new URI(base);
25192    }
25193
25194    if (!resolved._parts.protocol) {
25195      resolved._parts.protocol = base._parts.protocol;
25196    }
25197
25198    if (this._parts.hostname) {
25199      return resolved;
25200    }
25201
25202    for (i = 0; (p = properties[i]); i++) {
25203      resolved._parts[p] = base._parts[p];
25204    }
25205
25206    if (!resolved._parts.path) {
25207      resolved._parts.path = base._parts.path;
25208      if (!resolved._parts.query) {
25209        resolved._parts.query = base._parts.query;
25210      }
25211    } else if (resolved._parts.path.substring(-2) === '..') {
25212      resolved._parts.path += '/';
25213    }
25214
25215    if (resolved.path().charAt(0) !== '/') {
25216      basedir = base.directory();
25217      resolved._parts.path = (basedir ? (basedir + '/') : '') + resolved._parts.path;
25218      resolved.normalizePath();
25219    }
25220
25221    resolved.build();
25222    return resolved;
25223  };
25224  p.relativeTo = function(base) {
25225    var relative = this.clone().normalize();
25226    var relativeParts, baseParts, common, relativePath, basePath;
25227
25228    if (relative._parts.urn) {
25229      throw new Error('URNs do not have any generally defined hierarchical components');
25230    }
25231
25232    base = new URI(base).normalize();
25233    relativeParts = relative._parts;
25234    baseParts = base._parts;
25235    relativePath = relative.path();
25236    basePath = base.path();
25237
25238    if (relativePath.charAt(0) !== '/') {
25239      throw new Error('URI is already relative');
25240    }
25241
25242    if (basePath.charAt(0) !== '/') {
25243      throw new Error('Cannot calculate a URI relative to another relative URI');
25244    }
25245
25246    if (relativeParts.protocol === baseParts.protocol) {
25247      relativeParts.protocol = null;
25248    }
25249
25250    if (relativeParts.username !== baseParts.username || relativeParts.password !== baseParts.password) {
25251      return relative.build();
25252    }
25253
25254    if (relativeParts.protocol !== null || relativeParts.username !== null || relativeParts.password !== null) {
25255      return relative.build();
25256    }
25257
25258    if (relativeParts.hostname === baseParts.hostname && relativeParts.port === baseParts.port) {
25259      relativeParts.hostname = null;
25260      relativeParts.port = null;
25261    } else {
25262      return relative.build();
25263    }
25264
25265    if (relativePath === basePath) {
25266      relativeParts.path = '';
25267      return relative.build();
25268    }
25269
25270    // determine common sub path
25271    common = URI.commonPath(relative.path(), base.path());
25272
25273    // If the paths have nothing in common, return a relative URL with the absolute path.
25274    if (!common) {
25275      return relative.build();
25276    }
25277
25278    var parents = baseParts.path
25279      .substring(common.length)
25280      .replace(/[^\/]*$/, '')
25281      .replace(/.*?\//g, '../');
25282
25283    relativeParts.path = parents + relativeParts.path.substring(common.length);
25284
25285    return relative.build();
25286  };
25287
25288  // comparing URIs
25289  p.equals = function(uri) {
25290    var one = this.clone();
25291    var two = new URI(uri);
25292    var one_map = {};
25293    var two_map = {};
25294    var checked = {};
25295    var one_query, two_query, key;
25296
25297    one.normalize();
25298    two.normalize();
25299
25300    // exact match
25301    if (one.toString() === two.toString()) {
25302      return true;
25303    }
25304
25305    // extract query string
25306    one_query = one.query();
25307    two_query = two.query();
25308    one.query('');
25309    two.query('');
25310
25311    // definitely not equal if not even non-query parts match
25312    if (one.toString() !== two.toString()) {
25313      return false;
25314    }
25315
25316    // query parameters have the same length, even if they're permuted
25317    if (one_query.length !== two_query.length) {
25318      return false;
25319    }
25320
25321    one_map = URI.parseQuery(one_query, this._parts.escapeQuerySpace);
25322    two_map = URI.parseQuery(two_query, this._parts.escapeQuerySpace);
25323
25324    for (key in one_map) {
25325      if (hasOwn.call(one_map, key)) {
25326        if (!isArray(one_map[key])) {
25327          if (one_map[key] !== two_map[key]) {
25328            return false;
25329          }
25330        } else if (!arraysEqual(one_map[key], two_map[key])) {
25331          return false;
25332        }
25333
25334        checked[key] = true;
25335      }
25336    }
25337
25338    for (key in two_map) {
25339      if (hasOwn.call(two_map, key)) {
25340        if (!checked[key]) {
25341          // two contains a parameter not present in one
25342          return false;
25343        }
25344      }
25345    }
25346
25347    return true;
25348  };
25349
25350  // state
25351  p.duplicateQueryParameters = function(v) {
25352    this._parts.duplicateQueryParameters = !!v;
25353    return this;
25354  };
25355
25356  p.escapeQuerySpace = function(v) {
25357    this._parts.escapeQuerySpace = !!v;
25358    return this;
25359  };
25360
25361  return URI;
25362}));
25363/*
25364    Copyright 2017 Rustici Software
25365
25366    See the LICENSE.md, you may not use this file except in compliance with the License.
25367
25368    Unless required by applicable law or agreed to in writing, softw
25368are
25369    distributed under the License is distributed on an "AS IS" BASIS,
25370    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
25371    See the License for the specific language governing permissions and
25372    limitations under the License.
25373*/
25374
25375/**
25376cmi5.js AU runtime library
25377
25378@module Cmi5
25379*/
25380var Cmi5;
25381
25382(function () {
25383    "use strict";
25384    var THIS_LIBRARY = {
25385            // set by the build step
25386            VERSION: "<%= pkg.version %>",
25387            NAME: "<%= pkg.name %>",
25388            DESCRIPTION: "<%= pkg.description %>"
25389        },
25390        nativeRequest,
25391        xdrRequest,
25392        requestComplete,
25393        __delay,
25394        env = {},
25395        STATE_LMS_LAUNCHDATA = "LMS.LaunchData",
25396        LAUNCH_MODE_NORMAL = "Normal",
25397        AGENT_PROFILE_LEARNER_PREFS = "cmi5LearnerPreferences",
25398        CATEGORY_ACTIVITY_CMI5 = new TinCan.Activity(
25399            {
25400                id: "https://w3id.org/xapi/cmi5/context/categories/cmi5"
25401            }
25402        ),
25403        CATEGORY_ACTIVITY_MOVEON = new TinCan.Activity(
25404            {
25405                id: "https://w3id.org/xapi/cmi5/context/categories/moveon"
25406            }
25407        ),
25408        OTHER_ACTIVITY_CMI5JS = new TinCan.Activity(
25409            {
25410                id: "http://id.tincanapi.com/activity/software/" + THIS_LIBRARY.NAME + "/" + THIS_LIBRARY.VERSION,
25411                definition: {
25412                    name: {
25413                        und: THIS_LIBRARY.NAME + " (" + THIS_LIBRARY.VERSION + ")"
25414                    },
25415                    description: {
25416                        "en": THIS_LIBRARY.DESCRIPTION
25417                    },
25418                    type: "http://id.tincanapi.com/activitytype/source"
25419                }
25420            }
25421        ),
25422        EXTENSION_SESSION_ID = "https://w3id.org/xapi/cmi5/context/extensions/sessionid",
25423        EXTENSION_MASTERY_SCORE = "https://w3id.org/xapi/cmi5/context/extensions/masteryscore",
25424        VERB_INITIALIZED_ID = "http://adlnet.gov/expapi/verbs/initialized",
25425        VERB_TERMINATED_ID = "http://adlnet.gov/expapi/verbs/terminated",
25426        VERB_COMPLETED_ID = "http://adlnet.gov/expapi/verbs/completed",
25427        VERB_PASSED_ID = "http://adlnet.gov/expapi/verbs/passed",
25428        VERB_FAILED_ID = "http://adlnet.gov/expapi/verbs/failed",
25429        verbDisplay = {},
25430        launchParameters = [
25431            "endpoint",
25432            "fetch",
25433            "actor",
25434            "activityId",
25435            "registration"
25436        ],
25437        isInteger;
25438
25439    // polyfill for Number.isInteger from MDN
25440    isInteger = function (value) {
25441        return typeof value === "number" && isFinite(value) && Math.floor(value) === value;
25442    };
25443
25444    verbDisplay[VERB_INITIALIZED_ID] = {
25445        "en": "initialized"
25446    };
25447    verbDisplay[VERB_TERMINATED_ID] = {
25448        "en": "terminated"
25449    };
25450
25451    //
25452    // Detect CORS and XDR support
25453    //
25454    env.hasCORS = false;
25455    env.useXDR = false;
25456
25457    if (typeof XMLHttpRequest !== "undefined" && typeof (new XMLHttpRequest()).withCredentials !== "undefined") {
25458        env.hasCORS = true;
25459    }
25460    else if (typeof XDomainRequest !== "undefined") {
25461        env.hasCORS = true;
25462        env.useXDR = true;
25463    }
25464
25465    /**
25466        Top level interface constructor.
25467
25468        It is highly recommended to use asynchronous calls to methods supporting a callback.
25469
25470        @class Cmi5
25471        @constructor
25472        @param {String} [launchString] AU Launch URL providing configuration options
25473        @throws {Error} Invalid launch string
25474    */
25475    Cmi5 = function (launchString) {
25476        this.log("constructor", launchString);
25477        var url,
25478            cfg,
25479            i;
25480
25481        if (typeof launchString !== "undefined") {
25482            url = new URI(launchString);
25483            cfg = url.search(true);
25484
25485            for (i = 0; i < launchParameters.length; i += 1) {
25486                if (typeof cfg[launchParameters[i]] === "undefined" || cfg[launchParameters[i]] === "") {
25487                    throw new Error("Invalid launch string missing or empty parameter: " + launchParameters[i]);
25488                }
25489            }
25490
25491            this.setFetch(cfg.fetch);
25492            this.setLRS(cfg.endpoint);
25493            this.setActor(cfg.actor);
25494            this.setActivity(cfg.activityId);
25495            this.setRegistration(cfg.registration);
25496        }
25497    };
25498
25499    /**
25500        Version of this library
25501
25502        @property VERSION
25503        @static
25504        @type String
25505    */
25506    Cmi5.VERSION = THIS_LIBRARY.VERSION;
25507
25508    /**
25509        Whether or not to enable debug logging
25510
25511        @property DEBUG
25512        @static
25513        @type Boolean
25514        @default false
25515    */
25516    Cmi5.DEBUG = false;
25517
25518    Cmi5.prototype = {
25519        _fetch: null,
25520        _endpoint: null,
25521        _actor: null,
25522        _registration: null,
25523        _activity: null,
25524
25525        _lrs: null,
25526        _fetchRequest: null,
25527        _fetchContent: null,
25528        _lmsLaunchData: null,
25529        _contextTemplate: null,
25530        _learnerPrefs: null,
25531        _isActive: false,
25532        _initialized: null,
25533        _passed: null,
25534        _failed: null,
25535        _completed: null,
25536        _terminated: null,
25537        _durationStart: null,
25538        _progress: null,
25539        _includeSourceActivity: true,
25540
25541        /**
25542            Method to call to start the AU runtime
25543
25544            This is a simplified "boot" sequence for the AU that will call the individual parts of the start up sequence that would otherwise need to be called in order sequentially.
25545
25546            @method start
25547            @param {Function} callback Function to call on error or success
25548            @param {Object} [events] Functions to run at specific execution points
25549                @param {Function} [events.postFetch] Function to run after retrieving fetchUrl result
25550                @param {Function} [events.launchData] Function to run after retrieving launch data
25551                @param {Function} [events.learnerPrefs] Function to run after retrieving learner preferences
25552                @param {Function} [events.initializeStatement] Function to run after saving initialization statement
25553        */
25554        start: function (callback, events) {
25555            this.log("start");
25556            var self = this;
25557
25558            events = events || {};
25559
25560            self.postFetch(
25561                function (err) {
25562                    var prefix = "Failed to start AU - ";
25563
25564                    if (typeof events.postFetch !== "undefined") {
25565                        events.postFetch.apply(this, arguments);
25566                    }
25567                    if (err !== null) {
25568                        callback(new Error(prefix + " POST to fetch: " + err));
25569                        return;
25570                    }
25571
25572                    self.loadLMSLaunchData(
25573                        function (err) {
25574                            if (typeof events.launchData !== "undefined") {
25575                                events.launchData.apply(this, arguments);
25576                            }
25577                            if (err !== null) {
25578                                callback(new Error(prefix + " load LMS LaunchData: " + err));
25579                                return;
25580                            }
25581
25582                            self.loadLearnerPrefs(
25583                                function (err) {
25584                                    if (typeof events.learnerPrefs !== "undefined") {
25585                                        events.learnerPrefs.apply(this, arguments);
25586                                    }
25587                                    if (err !== null) {
25588                                        callback(new Error(prefix + " load learner preferences: " + err));
25589                                        return;
25590                                    }
25591
25592                                    self.initialize(
25593                                        function (err) {
25594                                            if (typeof events.initializeStatement !== "undefined") {
25595                                                events.initializeStatement.apply(this, arguments);
25596                                            }
25597                                            if (err !== null) {
25598                                                callback(new Error(prefix + " send initialized statement: " + err));
25599                                                return;
25600                                            }
25601
25602                                            callback(null);
25603                                        }
25604                                    );
25605                                }
25606                            );
25607                        }
25608                    );
25609                }
25610            );
25611        },
25612
25613        /**
25614            Method to POST to the fetchUrl to retrieve the LRS credentials
25615
25616            `setFetch` has to be called first and is called by the constructor if the launch string was provided to it.
25617
25618            @method postFetch
25619            @param {Function} [callback] Function to call on error or success
25620        */
25621        postFetch: function (callback) {
25622            this.log("postFetch");
25623            var self = this,
25624                cbWrapper;
25625
25626            if (this._fetch === null) {
25627                callback(new Error("Can't POST to fetch URL without setFetch"));
25628                return;
25629            }
25630
25631            if (callback) {
25632                cbWrapper = function (err, xhr) {
25633                    self.log("postFetch::cbWrapper");
25634                    self.log("postFetch::cbWrapper", err);
25635                    self.log("postFetch::cbWrapper", xhr);
25636                    var parsed,
25637                        responseContent = xhr.responseText,
25638                        responseContentType;
25639
25640                    if (err !== null) {
25641                        if (err === 0) {
25642                            err = "Aborted, offline, or invalid CORS endpoint";
25643                        }
25644                        else if (/^\d+$/.test(err)) {
25645                            if (typeof xhr.getResponseHeader !== "undefined") {
25646                                responseContentType = xhr.getResponseHeader("Content-Type");
25647                            }
25648                            else if (typeof xhr.contentType !== "undefined") {
25649                                responseContentType = xhr.contentType;
25650                            }
25651                            if (TinCan.Utils.isApplicationJSON(responseContentType)) {
25652                                try {
25653                                    parsed = JSON.parse(responseContent);
25654
25655                                    if (typeof parsed["error-text"] !== "undefined") {
25656                                        err = parsed["error-text"] + " (" + parsed["error-code"] + ")";
25657                                    }
25658                                    else {
25659                                        err = "Failed to detect 'error-text' property in JSON error response";
25660                                    }
25661                                }
25662                                catch (ex) {
25663                                    err = "Failed to parse JSON error response: " + ex;
25664                                }
25665                            }
25666                            else {
25667                                err = xhr.responseText;
25668                            }
25669                        }
25670                        else {
25671                            err = xhr.responseText;
25672                        }
25673                        callback(new Error(err), xhr, parsed);
25674                        return;
25675                    }
25676
25677                    try {
25678                        parsed = JSON.parse(responseContent);
25679                    }
25680                    catch (ex) {
25681                        self.log("postFetch::cbWrapper - failed to parse JSON response: " + ex);
25682                        callback(new Error("Post fetch response malformed: failed to parse JSON response 
25682(" + ex + ")"), xhr);
25683                        return;
25684                    }
25685
25686                    if (parsed === null || typeof parsed !== "object" || typeof parsed["auth-token"] === "undefined") {
25687                        self.log("postFetch::cbWrapper - failed to access 'auth-token' property");
25688                        callback(new Error("Post fetch response malformed: failed to access 'auth-token' in (" + responseContent + ")"), xhr, parsed);
25689                        return;
25690                    }
25691
25692                    self._fetchContent = parsed;
25693                    self._lrs.auth = "Basic " + parsed["auth-token"];
25694
25695                    callback(err, xhr, parsed);
25696                };
25697            }
25698
25699            return this._fetchRequest(
25700                this._fetch,
25701                {
25702                    method: "POST"
25703                },
25704                cbWrapper
25705            );
25706        },
25707
25708        /**
25709            Method to load the LMS.LaunchData state document populated by the LMS
25710
25711            Fetch data has to have already been loaded, in order to have LRS credential.
25712
25713            @method loadLMSLaunchData
25714            @param {Function} callback Function to call on error or success
25715        */
25716        loadLMSLaunchData: function (callback) {
25717            this.log("loadLMSLaunchData");
25718            var self = this;
25719
25720            if (this._fetchContent === null) {
25721                callback(new Error("Can't retrieve LMS Launch Data without successful postFetch"));
25722                return;
25723            }
25724
25725            this._lrs.retrieveState(
25726                STATE_LMS_LAUNCHDATA,
25727                {
25728                    activity: this._activity,
25729                    agent: this._actor,
25730                    registration: this._registration,
25731                    callback: function (err, result) {
25732                        if (err !== null) {
25733                            callback(new Error("Failed to retrieve " + STATE_LMS_LAUNCHDATA + " State: " + err), result);
25734                            return;
25735                        }
25736
25737                        //
25738                        // a missing state isn't an error as far as TinCanJS is concerned, but
25739                        // getting a 404 on the LMS LaunchData is a problem in cmi5 so fail here
25740                        // in that case (which is when result is null)
25741                        //
25742                        if (result === null) {
25743                            callback(new Error(STATE_LMS_LAUNCHDATA + " State not found"), result);
25744                            return;
25745                        }
25746
25747                        self._lmsLaunchData = result.contents;
25748
25749                        //
25750                        // store a stringified version of the context template for cheap
25751                        // cloning when we go to prepare it later for use in statements
25752                        //
25753                        self._contextTemplate = JSON.stringify(self._lmsLaunchData.contextTemplate);
25754
25755                        callback(null, result);
25756                    }
25757                }
25758            );
25759        },
25760
25761        /**
25762            Method to load learner prefs agent profile document possibly populated by the LMS
25763
25764            @method loadLearnerPrefs
25765            @param {Function} callback Function to call on error or success
25766        */
25767        loadLearnerPrefs: function (callback) {
25768            this.log("loadLearnerPrefs");
25769            var self = this;
25770
25771            if (this._lmsLaunchData === null) {
25772                callback(new Error("Can't retrieve Learner Preferences without successful loadLMSLaunchData"));
25773                return;
25774            }
25775
25776            this._lrs.retrieveAgentProfile(
25777                AGENT_PROFILE_LEARNER_PREFS,
25778                {
25779                    agent: this._actor,
25780                    callback: function (err, result) {
25781                        if (err !== null) {
25782                            callback(new Error("Failed to retrieve " + AGENT_PROFILE_LEARNER_PREFS + " Agent Profile" + err), result);
25783                            return;
25784                        }
25785
25786                        //
25787                        // result is null when the profile 404s which is not an error,
25788                        // just means it hasn't been set to anything
25789                        //
25790                        if (result !== null) {
25791                            self._learnerPrefs = result;
25792                        }
25793                        else {
25794                            //
25795                            // store an empty object locally to be able to distinguish a non-set
25796                            // preference document vs a non-fetched preference document
25797                            //
25798                            self._learnerPrefs = new TinCan.AgentProfile(
25799                                {
25800                                    id: AGENT_PROFILE_LEARNER_PREFS,
25801                                    contentType: "application/json",
25802                                    contents: {}
25803                                }
25804                            );
25805                        }
25806
25807                        callback(null, result);
25808                    }
25809                }
25810            );
25811        },
25812
25813        /**
25814            Method to save learner prefs to agent profile document in LRS
25815
25816            @method saveLearnerPrefs
25817            @param {Function} [callback] Function to call on error or success
25818        */
25819        saveLearnerPrefs: function (callback) {
25820            this.log("saveLearnerPrefs");
25821            var self = this,
25822                result,
25823                cbWrapper;
25824
25825            if (this._learnerPrefs === null) {
25826                result = new Error("Can't save Learner Preferences without first loading them");
25827                if (callback) {
25828                    callback(result);
25829                    return;
25830                }
25831                return result;
25832            }
25833
25834            if (callback) {
25835                cbWrapper = function (err, result) {
25836                    self.log("saveLearnerPrefs - saveAgentProfile callback", err, result);
25837                    if (err !== null) {
25838                        callback(new Error("Failed to save " + AGENT_PROFILE_LEARNER_PREFS + " Agent Profile: " + err), result);
25839                        return;
25840                    }
25841
25842                    self._learnerPrefs.etag = TinCan.Utils.getSHA1String(
25843                        (typeof self._learnerPrefs.contents === "object" && TinCan.Utils.isApplicationJSON(self._learnerPrefs.contentType))
25844                            ? JSON.stringify(self._learnerPrefs.contents)
25845                            : self._learnerPrefs.contents
25846                    );
25847
25848                    callback(null, result);
25849                };
25850            }
25851
25852            result = this._lrs.saveAgentProfile(
25853                AGENT_PROFILE_LEARNER_PREFS,
25854                this._learnerPrefs.contents,
25855                {
25856                    agent: this._actor,
25857                    lastSHA1: this._learnerPrefs.etag,
25858                    contentType: this._learnerPrefs.contentType,
25859                    callback: cbWrapper
25860                }
25861            );
25862            if (cbWrapper) {
25863                return;
25864            }
25865
25866            if (result.err !== null) {
25867                return new Error("Failed to save " + AGENT_PROFILE_LEARNER_PREFS + " Agent Profile: " + result.err);
25868            }
25869
25870            self._learnerPrefs.etag = TinCan.Utils.getSHA1String(
25871                (typeof self._learnerPrefs.contents === "object" && TinCan.Utils.isApplicationJSON(self._learnerPrefs.contentType))
25872                    ? JSON.stringify(self._learnerPrefs.contents)
25873                    : self._learnerPrefs.contents
25874            );
25875
25876            return;
25877        },
25878
25879        /**
25880            Finalize initialization process by sending initialized statement, starting duration tracker, and marking AU active
25881
25882            @method initialize
25883            @param {Function} [callback] Function to call on error or success
25884            @throws {Error} <ul><li>Learner prefs not loaded</li><li>AU already initialized</li></ul>
25885        */
25886        initialize: function (callback) {
25887            this.log("initialize");
25888            var st,
25889                err,
25890                callbackWrapper,
25891                result;
25892
25893            if (this._learnerPrefs === null) {
25894                err = new Error("Can't send initialized statement without successful loadLearnerPrefs");
25895                if (callback) {
25896                    callback(err);
25897                    return;
25898                }
25899
25900                throw err;
25901            }
25902
25903            if (this._initialized) {
25904                this.log("initialize - already initialized");
25905
25906                err = new Error("AU already initialized");
25907                if (callback) {
25908                    callback(err);
25909                    return;
25910                }
25911
25912                throw err;
25913            }
25914
25915            st = this.initializedStatement();
25916
25917            if (callback) {
25918                callbackWrapper = function (err) {
25919                    this.log("initialize - callbackWrapper: " + err);
25920                    if (err === null) {
25921                        this._initialized = true;
25922                        this._isActive = true;
25923                        this._durationStart = new Date().getTime();
25924                    }
25925
25926                    callback.apply(this, arguments);
25927                }.bind(this);
25928            }
25929
25930            result = this.sendStatement(st, callbackWrapper);
25931            this.log("initialize - result: ", result);
25932
25933            if (! callback && result.response.err === null) {
25934                this._initialized = true;
25935                this._isActive = true;
25936                this._durationStart = new Date().getTime();
25937            }
25938
25939            return result;
25940        },
25941
25942        /**
25943            Method to indicate session termination should occur, sends terminated statement, marks AU inactive
25944
25945            @method terminate
25946            @param {Function} [callback] Function to call on error or success
25947            @throws {Error} <ul><li>AU not initialized</li><li>AU already terminated</li></ul>
25948        */
25949        terminate: function (callback) {
25950            this.log("terminate");
25951            var st,
25952                err,
25953                callbackWrapper,
25954                result;
25955
25956            if (! this._initialized) {
25957                this.log("terminate - not initialized");
25958
25959                err = new Error("AU not initialized");
25960                if (callback) {
25961                    callback(err);
25962                    return;
25963                }
25964
25965                throw err;
25966            }
25967
25968            if (this._terminated) {
25969                this.log("terminate - already terminated");
25970
25971                err = new Error("AU already terminated");
25972                if (callback) {
25973                    callback(err);
25974                    return;
25975                }
25976
25977                throw err;
25978            }
25979
25980            st = this.terminatedStatement();
25981
25982            if (callback) {
25983                callbackWrapper = function (err) {
25984                    this.log("terminate - callbackWrapper: " + err);
25985                    if (err === null) {
25986                        this._terminated = true;
25987                        this._isActive = false;
25988                    }
25989
25990                    callback.apply(this, arguments);
25991                }.bind(this);
25992            }
25993
25994            result = this.sendStatement(st, callbackWrapper);
25995            this.log("terminate - result: ", result);
25996
25997            if (! callback && result.response.err === null) {
25998                this._terminated = true;
25999                this._isActive = false;
26000            }
26001
26002            return result;
26003        },
26004
26005        /**
26006            Method to indicate learner has completed the AU, sends completed statement
26007
26008            @method completed
26009            @param {Function} [callback] Function to call on error or success
26010            @throws {Error} <ul><li>AU not active</li><li>AU not in normal launch mode</li><li>AU already completed</li></ul>
26011        */
26012        completed: function (callback) {
26013            this.log("completed");
26014            var st,
26015                err,
26016                callbackWrapper,
26017                result;
26018
26019            if (! this.isActive()) {
26020                this.log("completed - not active");
26021                err = new Error("AU not active");
26022
26023                if (callback) {
26024                    callback(err);
26025                    return;
26026                }
26027
26028                throw err;
26029            }
26030
26031            if (this.getLaunchMode() !== LAUNCH_MODE_NORMAL) {
26032                this.log("completed - non-Normal launch mode: ", this.getLaunchMode());
26033                err = new Error("AU not in Normal launch mode");
26034
26035                if (callback) {
26036                    callback(err);
26037                    return;
26038                }
26039
26040                throw err;
26041            }
26042
26043            if (this._completed) {
26044                this.log("completed - already completed");
26045                err = new Error("AU already completed");
26046
26047                if (callback) {
26048                    callback(err);
26049                    return;
26050                }
26051
26052                throw err;
26053            }
26054
26055            st = this.completedStatement();
26056
26057            if (callback) {
26058                callbackWrapper = function (err) {
26059                    this.log("completed - callbackWrapper: " + err);
26060                    if (err === null) {
26061                        this.setProgress(null);
26062                        this._completed = true;
26063                    }
26064
26065                    callback.apply(this, arguments);
26066                }.bind(this);
26067            }
26068
26069            result = this.sendStatement(st, callbackWrapper);
26070            this.log("completed - result: ", result);
26071
26072            if (! callback && result.response.err === null) {
26073                this.setProgress(null);
26074                this._completed = true;
26075            }
26076
26077            return result;
26078        },
26079
26080        /**
26081            Method to indicate learner has passed the AU, sends passed statement with optional score
26082
26083            @method passed
26084            @param {Object} [score] Score to be included in statement (see `passedStatement`)
26085            @param {Function} [callback] Function to call on error or success
26086            @throws {Error} <ul><li>AU not active,</li><li>AU not in normal launch mode,</li><li>AU already passed,</li><li>Failed to create passed statement (usually because of malformed score)</li></ul>
26087        */
26088        passed: function (score, callback) {
26089            this.log("passed");
26090            var st,
26091                err,
26092                callbackWrapper,
26093                result;
26094
26095            if (! this.isActive()) {
26096                this.log("passed - not active");
26097                err = new Error("AU not active");
26098
26099                if (callback) {
26100                    callback(err);
26101                    return;
26102                }
26103
26104                throw err;
26105            }
26106
26107            if (this.getLaunchMode() !== LAUNCH_MODE_NORMAL) {
26108                this.log("passed - non-Normal launch mode: ", this.getLaunchMode());
26109                err = new Error("AU not in Normal launch mode");
26110
26111                if (callback) {
26112                    callback(err);
26113                    return;
26114                }
26115
26116                throw err;
26117            }
26118
26119            if (this._passed !== null) {
26120                this.log("passed - already passed");
26121                err = new Error("AU already passed");
26122
26123                if (callback) {
26124                    callback(err);
26125                    return;
26126                }
26127
26128                throw err;
26129            }
26130
26131            try {
26132                st = this.passedStatement(score);
26133            }
26134            catch (ex) {
26135                this.log("passed - failed to create passed statement: " + ex);
26136                if (callback) {
26137                    callback("Failed to create passed statement - " + ex);
26138                    return;
26139                }
26140
26141                throw ex;
26142            }
26143
26144            if (callback) {
26145                callbackWrapper = function (err) {
26146                    this.log("passed - callbackWrapper: " + err);
26147                    if (err === null) {
26148                        this._passed = true;
26149                    }
26150
26151                    callback.apply(this, arguments);
26152                }.bind(this);
26153            }
26154
26155            result = this.sendStatement(st, callbackWrapper);
26156            this.log("passed - result: ", result);
26157
26158            if (! callback && result.response.err === null) {
26159                this._passed = true;
26160            }
26161
26162            return result;
26163        },
26164
26165        /**
26166            Method to indicate learner has failed the AU, sends failed statement with optional score
26167
26168            @method failed
26169            @param {Object} [score] Score to be included in statement (see `failedStatement`)
26170            @param {Function} [callback] Function to call on error or success
26171            @throws {Error} <ul><li>AU not active</li><li>AU not in normal launch mode</li><li>AU already passed/failed</li><li>Failed to create failed statement (usually because of malformed score)</li></ul>
26172        */
26173        failed: function (score, callback) {
26174            this.log("failed");
26175            var st,
26176                err,
26177                callbackWrapper,
26178                result;
26179
26180            if (! this.isActive()) {
26181                this.log("failed - not active");
26182                err = new Error("AU not active");
26183
26184                if (callback) {
26185                    callback(err);
26186                    return;
26187                }
26188
26189                throw err;
26190            }
26191
26192            if (this.getLaunchMode() !== LAUNCH_MODE_NORMAL) {
26193                this.log("failed - non-Normal launch mode: ", this.getLaunchMode());
26194                err = new Error("AU not in Normal launch mode");
26195
26196                if (callback) {
26197                    callback(err);
26198                    return;
26199                }
26200
26201                throw err;
26202            }
26203
26204            if (this._failed !== null || this._passed !== null) {
26205                this.log("failed - already passed/failed");
26206                err = new Error("AU already passed/failed");
26207
26208                if (callback) {
26209                    callback(err);
26210                    return;
26211                }
26212
26213                throw err;
26214            }
26215
26216            try {
26217                st = this.failedStatement(score);
26218            }
26219            catch (ex) {
26220                this.log("failed - failed to create failed statement: " + ex);
26221                if (callback) {
26222                    callback("Failed to create failed statement - " + ex);
26223                    return;
26224                }
26225
26226                throw ex;
26227            }
26228
26229            if (callback) {
26230                callbackWrapper = function (err) {
26231                    this.log("failed - callbackWrapper: " + err);
26232                    if (err === null) {
26233                        this._failed = true;
26234                    }
26235
26236                    callback.apply(this, arguments);
26237                }.bind(this);
26238            }
26239
26240            result = this.sendStatement(st, callbackWrapper);
26241            this.log("failed - result: ", result);
26242
26243            if (! callback && result.response.err === null) {
26244                this._failed = true;
26245            }
26246
26247            return result;
26248        },
26249
26250        /**
26251            Method indicating whether the AU is currently active, has been initialized and not terminated
26252
26253            @method isActive
26254            @return {Boolean} Active flag
26255        */
26256        isActive: function () {
26257            this.log("isActive");
26258            return this._isActive;
26259        },
26260
26261        /**
26262            Safe version of logging, only displays when .DEBUG is true, and console.log
26263            is available
26264
26265            See `console.log` for parameters.
26266
26267            @method log
26268        */
26269        log: function () {
26270            /* eslint-disable no-console */
26271            if (Cmi5.DEBUG && typeof console !== "undefined" && console.log) {
26272                arguments[0] = "cmi5.js:" + arguments[0];
26273                console.log.apply(console, arguments);
26274            }
26275            /* eslint-enable no-console */
26276        },
26277
26278        /**
26279            Switch on/off whether a source activity is included in statements by default
26280
26281            Default: on
26282
26283            @method includeSourceActivity
26284            @param {Boolean} val true is include, false is exclude
26285        */
26286        includeSourceActivity: function (val) {
26287            this._includeSourceActivity = val ? true : false;
26288        },
26289
26290        /**
26291            Retrieve the launch method as provided in the LMS launch data
26292
26293            @method getLaunchMethod
26294            @throws {Error} LMS launch data has not been loaded
26295            @return {String} launch method
26296        */
26297        getLaunchMethod: function () {
26298            this.log("getLaunchMethod");
26299            if (this._lmsLaunchData === null) {
26300                throw new Error("Can't determine launchMethod until LMS LaunchData has been loaded");
26301            }
26302
26303            return this._lmsLaunchData.launchMethod;
26304        },
26305
26306        /**
26307            Retrieve the launch mode as provided in the LMS launch data
26308
26309            @method getLaunchMode
26310            @throws {Error} LMS launch data has not been loaded
26311            @return {String} launch mode
26312        */
26313        getLaunchMode: function () {
26314            this.log("getLaunchMode");
26315            if (this._lmsLaunchData === null) {
26316                throw new Error("Can't determine launchMode until LMS LaunchData has been loaded");
26317            }
26318
26319            return this._lmsLaunchData.launchMode;
26320        },
26321
26322        /**
26323            Retrieve the launch parameters when provided by the AU and in the launch data
26324
26325            @method getLaunchParameters
26326            @throws {Error} LMS launch data has not been loaded
26327            @return {String|null} launch parameters when exist or null
26328        */
26329        getLaunchParameters: function () {
26330            this.log("getLaunchParameters");
26331            var result = null;
26332
26333            if (this._lmsLaunchData === null) {
26334                throw new Error("Can't determine LaunchParameters until LMS LaunchData has been loaded");
26335            }
26336
26337            if (typeof this._lmsLaunchData.launchParameters !== "undefined") {
26338                result = this._lmsLaunchData.launchParameters;
26339            }
26340
26341            return result;
26342        },
26343
26344        /**
26345            Retrieve the session id
26346
26347            @method getSessionId
26348            @throws {Error} LMS launch data has not been loaded
26349            @return {String} session id
26350        */
26351        getSessionId: function () {
26352            this.log("getSessionId");
26353            if (this._lmsLaunchData === null) {
26354                throw new Error("Can't determine session id until LMS LaunchData has been loaded");
26355            }
26356
26357            return this._lmsLaunchData.contextTemplate.extensions[EXTENSION_SESSION_ID];
26358        },
26359
26360        /**
26361            Retrieve the moveOn value
26362
26363            @method getMoveOn
26364            @throws {Error} LMS launch data has not been loaded
26365            @return {String} moveOn value
26366        */
26367        getMoveOn: function () {
26368            this.log("getMoveOn");
26369            if (this._lmsLaunchData === null) {
26370                throw new Error("Can't determine moveOn until LMS LaunchData has been loaded");
26371            }
26372
26373            return this._lmsLaunchData.moveOn;
26374        },
26375
26376        /**
26377            Retrieve the mastery score as provided in LMS launch data
26378
26379            @method getMasteryScore
26380            @throws {Error} LMS launch data has not been loaded
26381            @return {String|null} mastery score or null
26382        */
26383        getMasteryScore: function () {
26384            this.log("getMasteryScore");
26385            var result = null;
26386
26387            if (this._lmsLaunchData === null) {
26388                throw new Error("Can't determine masteryScore until LMS LaunchData has been loaded");
26389            }
26390
26391            if (typeof this._lmsLaunchData.masteryScore !== "undefined") {
26392                result = this._lmsLaunchData.masteryScore;
26393            }
26394
26395            return result;
26396        },
26397
26398        /**
26399            Retrieve the return URL as provided in LMS launch data
26400
26401            @method getReturnURL
26402            @throws {Error} LMS launch data has not been loaded
26403            @return {String|null} mastery score or null
26404        */
26405        getReturnURL: function () {
26406            this.log("getReturnURL");
26407            var result = null;
26408
26409            if (this._lmsLaunchData === null) {
26410                throw new Error("Can't determine returnURL until LMS LaunchData has been loaded");
26411            }
26412
26413            if (typeof this._lmsLaunchData.returnURL !== "undefined") {
26414                result = this._lmsLaunchData.returnURL;
26415            }
26416
26417            return result;
26418        },
26419
26420        /**
26421            Retrieve the entitlement key as provided in LMS launch data
26422
26423            @method getEntitlementKey
26424            @throws {Error} LMS launch data has not been loaded
26425            @return {String|null} entitlement key
26426        */
26427        getEntitlementKey: function () {
26428            this.log("getEntitlementKey");
26429            var result = null;
26430
26431            if (this._lmsLaunchData === null) {
26432                throw new Error("Can't determine entitlementKey until LMS LaunchData has been loaded");
26433            }
26434
26435            if (typeof this._lmsLaunchData.entitlementKey !== "undefined") {
26436                if (typeof this._lmsLaunchData.entitlementKey.alternate !== "undefined") {
26437                    result = this._lmsLaunchData.entitlementKey.alternate;
26438                }
26439                else if (typeof this._lmsLaunchData.entitlementKey.courseStructure !== "undefined") {
26440                    result = this._lmsLaunchData.entitlementKey.courseStructure;
26441                }
26442            }
26443
26444            return result;
26445        },
26446
26447        /**
26448            Retrieve the language preference as provided in learner preferences
26449
26450            @method getLanguagePreference
26451            @throws {Error} Learner preference data has not been loaded
26452            @return {String|null} language preference
26453        */
26454        getLanguagePreference: function () {
26455            this.log("getLanguagePreference");
26456            var result = null;
26457
26458            if (this._learnerPrefs === null) {
26459                throw new Error("Can't determine language preference until learner preferences have been loaded");
26460            }
26461
26462            if (typeof this._learnerPrefs.contents.languagePreference !== "undefined") {
26463                result = this._learnerPrefs.contents.languagePreference;
26464            }
26465
26466            return result;
26467        },
26468
26469        /**
26470            Locally set the learner's language preference
26471
26472            @method setLanguagePreference
26473            @param {String} pref language preference code (use `""` to unset)
26474            @throws {Error} Learner preference data has not been loaded
26475        */
26476        setLanguagePreference: function (pref) {
26477            this.log("setLanguagePreference");
26478
26479            if (this._learnerPrefs === null) {
26480                throw new Error("Can't set language preference until learner preferences have been loaded");
26481            }
26482
26483            if (pref === "") {
26484                pref = null;
26485            }
26486
26487            this._learnerPrefs.contents.languagePreference = pref;
26488
26489            return;
26490        },
26491
26492        /**
26493            Retrieve the audio preference as provided in learner preferences
26494
26495            @method getAudioPreference
26496            @throws {Error} Learner preference data has not been loaded
26497            @return {String|null} audio preference
26498        */
26499        getAudioPreference: function () {
26500            this.log("getAudioPreference");
26501            var result = null;
26502
26503            if (this._learnerPrefs === null) {
26504                throw new Error("Can't determine audio preference until learner preferences have been loaded");
26505            }
26506
26507            if (typeof this._learnerPrefs.contents.audioPreference !== "undefined") {
26508                result = this._learnerPrefs.contents.audioPreference;
26509            }
26510
26511            return result;
26512        },
26513
26514        /**
26515            Locally set the learner's audio preference
26516
26517            @method setAudioPreference
26518            @param {String} pref "on", "off", or `null`
26519            @throws {Error} Learner preference data has not been loaded
26520        */
26521        setAudioPreference: function (pref) {
26522            this.log("setAudioPreference");
26523
26524            if (this._learnerPrefs === null) {
26525                throw new Error("Can't set audio preference until learner preferences have been loaded");
26526            }
26527
26528            if (pref !== "on" && pref !== "off" && pref !== null) {
26529                throw new Error("Unrecognized value for audio preference: " + pref);
26530            }
26531
26532            this._learnerPrefs.contents.audioPreference = pref;
26533
26534            return;
26535        },
26536
26537        /**
26538            Get the duration of this session so far
26539
26540            @method getDuration
26541            @return {Number} Number of milliseconds
26542        */
26543        getDuration: function () {
26544            this.log("getDuration");
26545
26546            return (new Date().getTime() - this._durationStart);
26547        },
26548
26549        /**
26550            Locally set the progress towards completion
26551
26552            @method setProgress
26553            @param {Integer} progress progress as a percentage between 0 and 100
26554            @throws {Error} <ul><li>Not an integer</li><li>Less than zero or greater than 100</li></ul>
26555        */
26556        setProgress: function (progress) {
26557            this.log("setProgress: ", progress);
26558
26559            if (progress !== null) {
26560                if (! isInteger(progress)) {
26561                    throw new Error("Invalid progress measure (not an integer): " + progress);
26562                }
26563                if (progress < 0 || progress > 100) {
26564                    throw new Error("Invalid progress measure must be greater than or equal to 0 and less than or equal to 100: " + progress);
26565                }
26566            }
26567            this._progress = progress;
26568        },
26569
26570        /**
26571            Get progress
26572
26573            @method getProgress
26574            @return {Integer|null} Integer value of locally set progress measure or null when not set
26575        */
26576        getProgress: function () {
26577            this.log("getProgress");
26578            return this._progress;
26579        },
26580
26581        /**
26582            Set the fetch URL, called by the `Cmi5` constructor when provided a launch URL
26583
26584            @method setFetch
26585            @param {String} fetchURL fetchURL as provided by the LMS in the launch string
26586        */
26587        setFetch: function (fetchURL) {
26588            this.log("setFetch: ", fetchURL);
26589            var urlParts,
26590                schemeMatches,
26591                locationPort,
26592                isXD;
26593
26594            this._fetch = fetchURL;
26595
26596            //
26597            // default to native request mode
26598            //
26599            this._fetchRequest = nativeRequest;
26600
26601            // TODO: swap this for uri.js
26602
26603            urlParts = fetchURL.toLowerCase().match(/([A-Za-z]+:)\/\/([^:\/]+):?(\d+)?(\/.*)?$/);
26604            if (urlParts === null) {
26605                throw new Error("URL invalid: failed to divide URL parts");
26606            }
26607
26608            //
26609            // determine whether this is a cross domain request,
26610            // whether our browser has CORS support at all, and then
26611            // if it does then if we are in IE with XDR only check that
26612            // the schemes match to see if we should be able to talk to
26613            // the other side
26614            //
26615            locationPort = location.port;
26616            schemeMatches = location.protocol.toLowerCase() === urlParts[1];
26617
26618            //
26619            // normalize the location.port cause it appears to be "" when 80/443
26620            // but our endpoint may have provided it
26621            //
26622            if (locationPort === "") {
26623                locationPort = (location.protocol.toLowerCase() === "http:" ? "80" : (location.protocol.toLowerCase() === "https:" ? "443" : ""));
26624            }
26625
26626            isXD = (
26627
26628                // is same scheme?
26629                ! schemeMatches
26630
26631                // is same host?
26632                || location.hostname.toLowerCase() !== urlParts[2]
26633
26634                // is same port?
26635                || locationPort !== (
26636                    (urlParts[3] !== null && typeof urlParts[3] !== "undefined" && urlParts[3] !== "")
26637                        ? urlParts[3]
26638                        : (urlParts[1] === "http:" ? "80" : (urlParts[1] === "https:" ? "443" : "")
26639                    )
26640                )
26641            );
26642            if (isXD) {
26643                if (env.hasCORS) {
26644                    if (env.useXDR && schemeMatches) {
26645                        this._fetchRequest = xdrRequest;
26646                    }
26647                    else if (env.useXDR && ! schemeMatches) {
26648                        this.log("[error] URL invalid: cross domain request for differing scheme in IE with XDR");
26649                        throw new Error("URL invalid: cross domain request for differing scheme in IE with XDR");
26650                    }
26651                }
26652                else {
26653                    this.log("[error] URL invalid: cross domain requests not supported in this browser");
26654                    throw new Error("URL invalid: cross domain requests not supported in this browser");
26655                }
26656            }
26657        },
26658
26659        /**
26660            Retrieve the fetch URL
26661
26662            @method getFetch
26663            @return {String} the previous set fetch URL
26664        */
26665        getFetch: function () {
26666            return this._fetch;
26667        },
26668
26669        /**
26670            Initialize the LRS to a `TinCan.LRS` object or update the existing object which will be used for all xAPI communications
26671
26672            Called by the `Cmi5` constructor when provided a launch URL.
26673
26674            @method setLRS
26675            @param {String} endpoint LRS location
26676            @param {String} auth Authentication token value
26677        */
26678        setLRS: function (endpoint, auth) {
26679            this.log("setLRS: ", endpoint, auth);
26680            if (this._lrs !== null) {
26681                if ((typeof auth === "undefined" && endpoint === null) || endpoint !== null) {
26682                    this._endpoint = this._lrs.endpoint = endpoint;
26683                }
26684                if (typeof auth !== "undefined" && auth !== null) {
26685                    this._lrs.auth = auth;
26686                }
26687            }
26688            else {
26689                this._lrs = new TinCan.LRS(
26690                    {
26691                        endpoint: endpoint,
26692                        auth: auth,
26693                        allowFail: false
26694                    }
26695                );
26696            }
26697        },
26698
26699        /**
26700            Retrieve the `TinCan.LRS` object
26701
26702            @method getLRS
26703            @return {TinCan.LRS} LRS object
26704        */
26705        getLRS: function () {
26706            return this._lrs;
26707        },
26708
26709        /**
26710            Initialize the actor using a `TinCan.Agent` that will represent the learner
26711
26712            Called by the `Cmi5` constructor when provided a launch URL.
26713
26714            @method setActor
26715            @param {String|TinCan.Agent} agent Pre-constructed Agent or string of JSON used to construct Agent
26716            @throws {Error} <ul><li>
26716Invalid actor, missing account IFI</li><li>Invalid account IFI</li></ul>
26717        */
26718        setActor: function (agent) {
26719            if (! (agent instanceof TinCan.Agent)) {
26720                agent = TinCan.Agent.fromJSON(agent);
26721            }
26722
26723            //
26724            // don't generally want to do too much validation as the LMS
26725            // should be giving us valid information, *but* in this case
26726            // users need to be able to count on the type of object being
26727            // returned
26728            //
26729            if ((agent.account === null) || (! (agent.account instanceof TinCan.AgentAccount))) {
26730                throw new Error("Invalid actor: missing or invalid account");
26731            }
26732            else if (agent.account.name === null) {
26733                throw new Error("Invalid actor: name is null");
26734            }
26735            else if (agent.account.name === "") {
26736                throw new Error("Invalid actor: name is empty");
26737            }
26738            else if (agent.account.homePage === null) {
26739                throw new Error("Invalid actor: homePage is null");
26740            }
26741            else if (agent.account.homePage === "") {
26742                throw new Error("Invalid actor: homePage is empty");
26743            }
26744
26745            this._actor = agent;
26746        },
26747
26748        /**
26749            Retrieve the `TinCan.Agent` object representing the learner
26750
26751            @method getActor
26752            @return {TinCan.Agent} Learner's Agent
26753        */
26754        getActor: function () {
26755            return this._actor;
26756        },
26757
26758        /**
26759            Initialize the root object representing the AU
26760
26761            Called by the `Cmi5` constructor when provided a launch URL.
26762
26763            @method setActivity
26764            @param {String|TinCan.Activity} activity Pre-constructed Activity or string id used to construct Activity
26765            @throws {Error} <ul><li>Invalid activity, null id</li><li>Invalid activity, empty string id</li></ul>
26766        */
26767        setActivity: function (activity) {
26768            if (! (activity instanceof TinCan.Activity)) {
26769                activity = new TinCan.Activity(
26770                    {
26771                        id: activity
26772                    }
26773                );
26774            }
26775
26776            if (activity.id === null) {
26777                throw new Error("Invalid activity: id is null");
26778            }
26779            else if (activity.id === "") {
26780                throw new Error("Invalid activity: id is empty");
26781            }
26782
26783            this._activity = activity;
26784        },
26785
26786        /**
26787            Retrieve the `TinCan.Activity` that is the root object representing the AU
26788
26789            @method getActivity
26790            @return {TinCan.Activity} Root Activity
26791        */
26792        getActivity: function () {
26793            return this._activity;
26794        },
26795
26796        /**
26797            Set the registration value
26798
26799            Called by the `Cmi5` constructor when provided a launch URL.
26800
26801            @method setRegistration
26802            @param {String} registration UUID representing the registration
26803            @throws {Error} <ul><li>Invalid registration, null</li><li>Invalid registration, empty string</li></ul>
26804        */
26805        setRegistration: function (registration) {
26806            if (registration === null) {
26807                throw new Error("Invalid registration: null");
26808            }
26809            else if (registration === "") {
26810                throw new Error("Invalid registration: empty");
26811            }
26812
26813            this._registration = registration;
26814        },
26815
26816        /**
26817            Retrieve the registration associated with the session
26818
26819            @method getRegistration
26820            @return {String} Registration
26821        */
26822        getRegistration: function () {
26823            return this._registration;
26824        },
26825
26826        /**
26827            Validate a Score object's properties
26828
26829            @method validateScore
26830            @param {TinCan.Score|Object} score Score object to validate
26831            @throws {Error} <ul><li>Null or missing score argument</li><li>Non-integer min or max value (when provided)</li><li>Non-number, negative, or greater than 1 scaled value (when provided)</li><li>Non-integer, missing or invalid min/max, raw value (when provided)</li></ul>
26832            @return {Boolean} true for passing, otherwise exception is thrown
26833        */
26834        validateScore: function (score) {
26835            if (typeof score === "undefined" || score === null) {
26836                throw new Error("cannot validate score (score not provided): " + score);
26837            }
26838
26839            if (typeof score.min !== "undefined") {
26840                if (! isInteger(score.min)) {
26841                    throw new Error("score.min is not an integer");
26842                }
26843            }
26844            if (typeof score.max !== "undefined") {
26845                if (! isInteger(score.max)) {
26846                    throw new Error("score.max is not an integer");
26847                }
26848            }
26849
26850            if (typeof score.scaled !== "undefined") {
26851                if (! /^(\-|\+)?[01]+(\.[0-9]+)?$/.test(score.scaled)) {
26852                    throw new Error("scaled score not a recognized number: " + score.scaled);
26853                }
26854
26855                if (score.scaled < 0) {
26856                    throw new Error("scaled score must be greater than or equal to 0");
26857                }
26858                if (score.scaled > 1) {
26859                    throw new Error("scaled score must be less than or equal to 1");
26860                }
26861            }
26862
26863            if (typeof score.raw !== "undefined") {
26864                if (! isInteger(score.raw)) {
26865                    throw new Error("score.raw is not an integer");
26866                }
26867                if (typeof score.min === "undefined") {
26868                    throw new Error("minimum score must be provided when including a raw score");
26869                }
26870                if (typeof score.max === "undefined") {
26871                    throw new Error("maximum score must be provided when including a raw score");
26872                }
26873                if (score.raw < score.min) {
26874                    throw new Error("raw score must be greater than or equal to minimum score");
26875                }
26876                if (score.raw > score.max) {
26877                    throw new Error("raw score must be less than or equal to maximum score");
26878                }
26879            }
26880
26881            return true;
26882        },
26883
26884        /**
26885            Prepare a cmi5 "allowed" statement including the actor, verb, object, and context
26886
26887            Used to construct a cmi5 "allowed" statement with all relevant information that can then be optionally added to prior to sending.
26888
26889            @method prepareStatement
26890            @param {String} verbId Verb identifier combined with other cmi5 pre-determined information (must be IRI)
26891            @return {TinCan.Statement} Statement
26892        */
26893        prepareStatement: function (verbId) {
26894            //
26895            // the specification allows for statements that are 'cmi5 allowed'
26896            // as oppposed to 'cmi5 defined' to be sent by the AU, so give the
26897            // AU the ability to get a prepared statement with a populated context
26898            // based on the template but without the category having been added
26899            //
26900            var stCfg = {
26901                    actor: this._actor,
26902                    verb: {
26903                        id: verbId
26904                    },
26905                    target: this._activity,
26906                    context: this._prepareContext()
26907                },
26908                progress = this.getProgress();
26909
26910            if (typeof verbDisplay[verbId] !== "undefined") {
26911                stCfg.verb.display = verbDisplay[verbId];
26912            }
26913
26914            if (verbId !== VERB_COMPLETED_ID && progress !== null) {
26915                stCfg.result = {
26916                    extensions: {
26917                        "https://w3id.org/xapi/cmi5/result/extensions/progress": progress
26918                    }
26919                };
26920            }
26921
26922            return new TinCan.Statement(stCfg);
26923        },
26924
26925        /**
26926            Store provided statement in the configured LRS
26927
26928            @method sendStatement
26929            @param {TinCan.Statement} st Statement to be stored
26930            @param {Function} [callback] Function to run on success/failure of statement save
26931        */
26932        sendStatement: function (st, callback) {
26933            var cbWrapper,
26934                result;
26935
26936            if (callback) {
26937                cbWrapper = function (err, result) {
26938                    if (err !== null) {
26939                        callback(new Error(err), result);
26940                        return;
26941                    }
26942
26943                    callback(err, result, st);
26944                };
26945            }
26946
26947            result = this._lrs.saveStatement(
26948                st,
26949                {
26950                    callback: cbWrapper
26951                }
26952            );
26953            if (! callback) {
26954                return {
26955                    response: result,
26956                    statement: st
26957                };
26958            }
26959        },
26960
26961        /*
26962         * The ...Statement methods are provided for users that want to implement
26963         * a queueing like mechansim or something similar where they are expected
26964         * to abide by the AU restrictions on what statements can be sent, etc. on
26965         * their own.
26966         *
26967         * (Such as in SCORM Driver which was the impetus for adding them.)
26968        */
26969
26970        /**
26971            Advanced Usage: retrieve prepared "initialized" statement
26972
26973            Statement methods are provided for users that want to implement
26974            a queueing like mechansim or something similar where they are expected
26975            to abide by the AU restrictions on what statements can be sent, etc. on
26976            their own.
26977
26978            @method initializedStatement
26979            @return {TinCan.Statement} Initialized statement
26980        */
26981        initializedStatement: function () {
26982            this.log("initializedStatement");
26983            return this._prepareStatement(VERB_INITIALIZED_ID);
26984        },
26985
26986        /**
26987            Advanced Usage: retrieve prepared "terminated" statement
26988
26989            Statement methods are provided for users that want to implement
26990            a queueing like mechansim or something similar where they are expected
26991            to abide by the AU restrictions on what statements can be sent, etc. on
26992            their own.
26993
26994            @method terminatedStatement
26995            @return {TinCan.Statement} Terminated statement
26996        */
26997        terminatedStatement: function () {
26998            this.log("terminatedStatement");
26999            var st = this._prepareStatement(VERB_TERMINATED_ID);
27000
27001            st.result = st.result || new TinCan.Result();
27002            st.result.duration = TinCan.Utils.convertMillisecondsToISO8601Duration(this.getDuration());
27003
27004            return st;
27005        },
27006
27007        /**
27008            Advanced Usage: retrieve prepared "passed" statement
27009
27010            Statement methods are provided for users that want to implement
27011            a queueing like mechansim or something similar where they are expected
27012            to abide by the AU restrictions on what statements can be sent, etc. on
27013            their own.
27014
27015            @method passedStatement
27016            @param {Object} [score] Object to be used as the score, must meet masteryScore requirements, etc.
27017            @return {TinCan.Statement} Passed statement
27018        */
27019        passedStatement: function (score) {
27020            this.log("passedStatement");
27021            var st = this._prepareStatement(VERB_PASSED_ID),
27022                masteryScore;
27023
27024            st.result = st.result || new TinCan.Result();
27025            st.result.success = true;
27026            st.result.duration = TinCan.Utils.convertMillisecondsToISO8601Duration(this.getDuration());
27027
27028            if (score) {
27029                try {
27030                    this.validateScore(score);
27031                }
27032                catch (ex) {
27033                    throw new Error("Invalid score - " + ex);
27034                }
27035
27036                masteryScore = this.getMasteryScore();
27037                if (masteryScore !== null && typeof score.scaled !== "undefined") {
27038                    if (score.scaled < masteryScore) {
27039                        throw new Error("Invalid score - scaled score does not meet or exceed mastery score (" + score.scaled + " < " + masteryScore + ")");
27040                    }
27041
27042                    st.context.extensions = st.context.extensions || {};
27043                    st.context.extensions[EXTENSION_MASTERY_SCORE] = masteryScore;
27044                }
27045
27046                st.result.score = new TinCan.Score(score);
27047            }
27048
27049            st.context.contextActivities.category.push(CATEGORY_ACTIVITY_MOVEON);
27050
27051            return st;
27052        },
27053
27054        /**
27055            Advanced Usage: retrieve prepared "failed" statement
27056
27057            Statement methods are provided for users that want to implement
27058            a queueing like mechansim or something similar where they are expected
27059            to abide by the AU restrictions on what statements can be sent, etc. on
27060            their own.
27061
27062            @method failedStatement
27063            @param {Object} [score] Object to be used as the score, must meet masteryScore requirements, etc.
27064            @return {TinCan.Statement} Failed statement
27065        */
27066        failedStatement: function (score) {
27067            this.log("failedStatement");
27068            var st = this._prepareStatement(VERB_FAILED_ID),
27069                masteryScore;
27070
27071            st.result = st.result || new TinCan.Result();
27072            st.result.success = false;
27073            st.result.duration = TinCan.Utils.convertMillisecondsToISO8601Duration(this.getDuration());
27074
27075            if (score) {
27076                try {
27077                    this.validateScore(score);
27078                }
27079                catch (ex) {
27080                    throw new Error("Invalid score - " + ex);
27081                }
27082
27083                masteryScore = this.getMasteryScore();
27084                if (masteryScore !== null && typeof score.scaled !== "undefined") {
27085                    if (score.scaled >= masteryScore) {
27086                        throw new Error("Invalid score - scaled score exceeds mastery score (" + score.scaled + " >= " + masteryScore + ")");
27087                    }
27088
27089                    st.context.extensions = st.context.extensions || {};
27090                    st.context.extensions[EXTENSION_MASTERY_SCORE] = masteryScore;
27091                }
27092
27093                st.result.score = new TinCan.Score(score);
27094            }
27095
27096            st.context.contextActivities.category.push(CATEGORY_ACTIVITY_MOVEON);
27097
27098            return st;
27099        },
27100
27101        /**
27102            Advanced Usage: retrieve prepared "completed" statement
27103
27104            Statement methods are provided for users that want to implement
27105            a queueing like mechansim or something similar where they are expected
27106            to abide by the AU restrictions on what statements can be sent, etc. on
27107            their own.
27108
27109            @method completedStatement
27110            @return {TinCan.Statement} Completed statement
27111        */
27112        completedStatement: function () {
27113            this.log("completedStatement");
27114            var st = this._prepareStatement(VERB_COMPLETED_ID);
27115
27116            st.result = st.result || new TinCan.Result();
27117            st.result.completion = true;
27118            st.result.duration = TinCan.Utils.convertMillisecondsToISO8601Duration(this.getDuration());
27119
27120            st.context.contextActivities.category.push(CATEGORY_ACTIVITY_MOVEON);
27121
27122            return st;
27123        },
27124
27125        /**
27126            @method _prepareContext
27127            @private
27128        */
27129        _prepareContext: function () {
27130            //
27131            // deserializing a string version of the template is slower
27132            // but gives us cheap cloning capability so that we don't
27133            // alter the template itself
27134            //
27135            var context = JSON.parse(this._contextTemplate);
27136
27137            context.registration = this._registration;
27138
27139            if (this._includeSourceActivity) {
27140                context.contextActivities = context.contextActivities || new TinCan.ContextActivities();
27141                context.contextActivities.other = context.contextActivities.other || [];
27142                context.contextActivities.other.push(OTHER_ACTIVITY_CMI5JS);
27143            }
27144
27145            return context;
27146        },
27147
27148        /**
27149            @method _prepareStatement
27150            @private
27151        */
27152        _prepareStatement: function (verbId) {
27153            //
27154            // statements sent by this lib are "cmi5 defined" statements meaning
27155            // they have the context category value added
27156            //
27157            var st = this.prepareStatement(verbId);
27158
27159            st.context.contextActivities = st.context.contextActivities || new TinCan.ContextActivities();
27160            st.context.contextActivities.category = st.context.contextActivities.category || [];
27161            st.context.contextActivities.category.push(CATEGORY_ACTIVITY_CMI5);
27162
27163            return st;
27164        }
27165    };
27166
27167    /**
27168        Turn on debug logging
27169
27170        @method enableDebug
27171        @static
27172        @param {Boolean} [includeTinCan] Whether to enable debug logging from TinCanJS
27173    */
27174    Cmi5.enableDebug = function (includeTinCan) {
27175        Cmi5.DEBUG = true;
27176
27177        if (includeTinCan) {
27178            TinCan.enableDebug();
27179        }
27180    };
27181
27182    /**
27183        Turn off debug logging
27184
27185        @method disableDebug
27186        @static
27187        @param {Boolean} [includeTinCan] Whether to disable debug logging from TinCanJS
27188    */
27189    Cmi5.disableDebug = function (includeTinCan) {
27190        Cmi5.DEBUG = false;
27191
27192        if (includeTinCan) {
27193            TinCan.disableDebug();
27194        }
27195    };
27196
27197    //
27198    // Setup request callback
27199    //
27200    requestComplete = function (xhr, cfg, control, callback) {
27201        this.log("requestComplete: " + control.finished + ", xhr.status: " + xhr.status);
27202        var requestCompleteResult,
27203            notFoundOk,
27204            httpStatus;
27205
27206        //
27207        // XDomainRequest doesn't give us a way to get the status,
27208        // so allow passing in a forged one
27209        //
27210        if (typeof xhr.status === "undefined") {
27211            httpStatus = control.fakeStatus;
27212        }
27213        else {
27214            //
27215            // older versions of IE don't properly handle 204 status codes
27216            // so correct when receiving a 1223 to be 204 locally
27217            // http://stackoverflow.com/questions/10046972/msie-returns-status-code-of-1223-for-ajax-request
27218            //
27219            httpStatus = (xhr.status === 1223) ? 204 : xhr.status;
27220        }
27221
27222        if (! control.finished) {
27223            // may be in sync or async mode, using XMLHttpRequest or IE XDomainRequest, onreadystatechange or
27224            // onload or both might fire depending upon browser, just covering all bases with event hooks and
27225            // using 'finished' flag to avoid triggering events multiple times
27226            control.finished = true;
27227
27228            notFoundOk = (cfg.ignore404 && httpStatus === 404);
27229            if ((httpStatus >= 200 && httpStatus < 400) || notFoundOk) {
27230                if (callback) {
27231                    callback(null, xhr);
27232                }
27233                else {
27234                    requestCompleteResult = {
27235                        err: null,
27236                        xhr: xhr
27237                    };
27238                    return requestCompleteResult;
27239                }
27240            }
27241            else {
27242                requestCompleteResult = {
27243                    err: httpStatus,
27244                    xhr: xhr
27245                };
27246                if (httpStatus === 0) {
27247                    this.log("[warning] There was a problem communicating with the server. Aborted, offline, or invalid CORS endpoint (" + httpStatus + ")");
27248                }
27249                else {
27250                    this.log("[warning] There was a problem communicating with the server. (" + httpStatus + " | " + xhr.responseText + ")");
27251                }
27252                if (callback) {
27253                    callback(httpStatus, xhr);
27254                }
27255                return requestCompleteResult;
27256            }
27257        }
27258        else {
27259            return requestCompleteResult;
27260        }
27261    };
27262
27263    //
27264    // one of the two of these is stuffed into the Cmi5 instance where a
27265    // request is needed which is fetch at the moment
27266    //
27267    nativeRequest = function (fullUrl, cfg, callback) {
27268        this.log("sendRequest using XMLHttpRequest");
27269        var self = this,
27270            xhr,
27271            prop,
27272            pairs = [],
27273            data,
27274            control = {
27275                finished: false,
27276                fakeStatus: null
27277            },
27278            async,
27279            fullRequest = fullUrl;
27280
27281        this.log("sendRequest using XMLHttpRequest - async: " + async);
27282
27283        cfg = cfg || {};
27284        cfg.params = cfg.params || {};
27285        cfg.headers = cfg.headers || {};
27286
27287        async = typeof callback !== "undefined";
27288
27289        for (prop in cfg.params) {
27290            if (cfg.params.hasOwnProperty(prop)) {
27291                pairs.push(prop + "=" + encodeURIComponent(cfg.params[prop]));
27292            }
27293        }
27294        if (pairs.length > 0) {
27295            fullRequest += "?" + pairs.join("&");
27296        }
27297
27298        xhr = new XMLHttpRequest();
27299
27300        xhr.open(cfg.method, fullRequest, async);
27301        for (prop in cfg.headers) {
27302            if (cfg.headers.hasOwnProperty(prop)) {
27303                xhr.setRequestHeader(prop, cfg.headers[prop]);
27304            }
27305        }
27306
27307        if (typeof cfg.data !== "undefined") {
27308            cfg.data += "";
27309        }
27310        data = cfg.data;
27311
27312        if (async) {
27313            xhr.onreadystatechange = function () {
27314                self.log("xhr.onreadystatechange - xhr.readyState: " + xhr.readyState);
27315                if (xhr.readyState === 4) {
27316                    requestComplete.call(self, xhr, cfg, control, callback);
27317                }
27318            };
27319        }
27320
27321        //
27322        // research indicates that IE is known to just throw exceptions
27323        // on .send and it seems everyone pretty much just ignores them
27324        // including jQuery (https://github.com/jquery/jquery/blob/1.10.2/src/ajax.js#L549
27325        // https://github.com/jquery/jquery/blob/1.10.2/src/ajax/xhr.js#L97)
27326        //
27327        try {
27328            xhr.send(data);
27329        }
27330        catch (ex) {
27331            this.log("sendRequest caught send exception: " + ex);
27332        }
27333
27334        if (async) {
27335            return;
27336        }
27337
27338        return requestComplete.call(this, xhr, cfg, control);
27339    };
27340    xdrRequest = function (fullUrl, cfg, callback) {
27341        this.log("sendRequest using XDomainRequest");
27342        var self = this,
27343            xhr,
27344            pairs = [],
27345            data,
27346            prop,
27347            until,
27348            control = {
27349                finished: false,
27350                fakeStatus: null
27351            },
27352            err;
27353
27354        cfg = cfg || {};
27355        cfg.params = cfg.params || {};
27356        cfg.headers = cfg.headers || {};
27357
27358        if (typeof cfg.headers["Content-Type"] !== "undefined" && cfg.headers["Content-Type"] !== "application/json") {
27359            err = new Error("Unsupported content type for IE Mode request");
27360            if (callback) {
27361                callback(err, null);
27362                return null;
27363            }
27364            return {
27365                err: err,
27366                xhr: null
27367            };
27368        }
27369
27370        for (prop in cfg.params) {
27371            if (cfg.params.hasOwnProperty(prop)) {
27372                pairs.push(prop + "=" + encodeURIComponent(cfg.params[prop]));
27373            }
27374        }
27375
27376        if (pairs.length > 0) {
27377            fullUrl += "?" + pairs.join("&");
27378        }
27379
27380        xhr = new XDomainRequest();
27381        xhr.open("POST", fullUrl);
27382
27383        if (! callback) {
27384            xhr.onload = function () {
27385                control.fakeStatus = 200;
27386            };
27387            xhr.onerror = function () {
27388                control.fakeStatus = 400;
27389            };
27390            xhr.ontimeout = function () {
27391                control.fakeStatus = 0;
27392            };
27393        }
27394        else {
27395            xhr.onload = function () {
27396                control.fakeStatus = 200;
27397                requestComplete.call(self, xhr, cfg, control, callback);
27398            };
27399            xhr.onerror = function () {
27400                control.fakeStatus = 400;
27401                requestComplete.call(self, xhr, cfg, control, callback);
27402            };
27403            xhr.ontimeout = function () {
27404                control.fakeStatus = 0;
27405                requestComplete.call(self, xhr, cfg, control, callback);
27406            };
27407        }
27408
27409        //
27410        // IE likes to randomly abort requests when some handlers
27411        // aren't defined, so define them with no-ops, see:
27412        //
27413        // http://cypressnorth.com/programming/internet-explorer-aborting-ajax-requests-fixed/
27414        // http://social.msdn.microsoft.com/Forums/ie/en-US/30ef3add-767c-4436-b8a9-f1ca19b4812e/ie9-rtm-xdomainrequest-issued-requests-may-abort-if-all-event-handlers-not-specified
27415        //
27416        xhr.onprogress = function () {};
27417        xhr.timeout = 0;
27418
27419        //
27420        // research indicates that IE is known to just throw exceptions
27421        // on .send and it seems everyone pretty much just ignores them
27422        // including jQuery (https://github.com/jquery/jquery/blob/1.10.2/src/ajax.js#L549
27423        // https://github.com/jquery/jquery/blob/1.10.2/src/ajax/xhr.js#L97)
27424        //
27425        try {
27426            xhr.send(data);
27427        }
27428        catch (ex) {
27429            this.log("sendRequest caught send exception: " + ex);
27430        }
27431
27432        if (! callback) {
27433            // synchronous call in IE, with no synchronous mode available
27434            until = 10000 + Date.now();
27435            this.log("sendRequest - until: " + until + ", finished: " + control.finished);
27436
27437            while (Date.now() < until && control.fakeStatus === null) {
27438                __delay();
27439            }
27440            return requestComplete.call(self, xhr, cfg, control);
27441        }
27442
27443        return;
27444    };
27445
27446    /**
27447        Non-environment safe method used to create a delay to give impression
27448        of synchronous response (for IE, shocker)
27449
27450        @method __delay
27451        @private
27452    */
27453    __delay = function () {
27454        //
27455        // use a synchronous request to the current location to allow the browser
27456        // to yield to the asynchronous request's events but still block in the
27457        // outer loop to make it seem synchronous to the end user
27458        //
27459        // removing this made the while loop too tight to allow the asynchronous
27460        // events through to get handled so that the response was correctly handled
27461        //
27462        var xhr = new XMLHttpRequest(),
27463            url = window.location + "?forcenocache=" + TinCan.Utils.getUUID();
27464
27465        xhr.open("GET", url, false);
27466        xhr.send(null);
27467    };
27468}());
27469/* declare globals that are set via Desktop token replacement for configuration */
27470var TC_COURSE_ID = "__TC_COURSE_ID__";
27471var TC_COURSE_NAME = "__TC_COURSE_NAME__";
27472var TC_COURSE_DESC = "__TC_COURSE_DESC__";
27473var TC_RECORD_STORES = "__TC_RECORD_STORES__";
27474
27475/* declare other globals we'll use */
27476var TCAPI_STATUS = "",
27477    TCAPI_STATUS_CHANGED = false,
27478    TCAPI_SCORE = {},
27479    TCAPI_COMPLETION_STATUS = "",
27480    TCAPI_SATISFACTION_STATUS = null,
27481    TCAPI_UPDATES_PENDING = false,
27482    TCAPI_IN_PROGRESS = false,
27483
27484    TCAPI_NO_ERROR = "",
27485    TCAPI_VERB_COMPLETED = "completed",
27486    TCAPI_VERB_EXPERIENCED = "experienced",
27487    TCAPI_VERB_ATTEMPTED = "attempted",
27488    TCAPI_VERB_ANSWERED = "answered",
27489    TCAPI_VERB_PASSED = "passed",
27490    TCAPI_VERB_FAILED = "failed",
27491    TCAPI_INIT_VERB = TCAPI_VERB_ATTEMPTED,
27492
27493    TCAPI_INTERACTION = "http://adlnet.gov/expapi/activities/cmi.interaction",
27494    TCAPI_INTERACTION_TYPE_TRUE_FALSE = "true-false",
27495    TCAPI_INTERACTION_TYPE_CHOICE = "choice",
27496    TCAPI_INTERACTION_TYPE_FILL_IN = "fill-in",
27497    TCAPI_INTERACTION_TYPE_MATCHING = "matching",
27498    TCAPI_INTERACTION_TYPE_PERFORMANCE = "performance",
27499    TCAPI_INTERACTION_TYPE_SEQUENCING = "sequencing",
27500    TCAPI_INTERACTION_TYPE_LIKERT = "likert",
27501    TCAPI_INTERACTION_TYPE_NUMERIC = "numeric",
27502
27503    TCAPI_STATE_BOOKMARK = "bookmark",
27504    TCAPI_STATE_TOTAL_TIME = "cumulative_time",
27505    TCAPI_STATE_SUSPEND_DATA = "suspend_data",
27506
27507    // these are non-HTTP status code errors from TinCanJS
27508    TCAPI_CONN_ERROR = 998,
27509    TCAPI_DEP_ERROR = 999,
27510
27511    TCAPI_ERROR_INVALID_PREFERENCE = 0,
27512    TCAPI_ERROR_INVALID_TIMESPAN   = 1,
27513
27514    TCAPI_FUNC_NOOP = function () { /* no-op */ },
27515
27516    intTCAPIError,
27517    strTCAPIErrorString,
27518    strTCAPIErrorDiagnostic;
27519
27520/* Declare global used for interacting with the LRS */
27521var tincan;
27522
27523/* Declare global used for our own local cache of items */
27524var tcapi_cache;
27525
27526function _TCAPI_HandleRequestErrorResult (result, mesg) {
27527    var errCode,
27528        errDiag,
27529        errMesg = mesg;
27530
27531    if (result.err !== null) {
27532        //
27533        // in the case of errors result.err either contains an Error object (theoretically)
27534        // or the HTTP status code if the request actually made it to the LRS, to distinguish
27535        // we can check it for all digits (which is safer than checking for an Error object,
27536        // at least in this version of TinCanJS), which then allows us to look at the response
27537        // text which is generally a more helpful message than the status code
27538        //
27539        if (/^\d+$/.test(result.err)) {
27540            if (result.err === 0) {
27541                // 0 status codes represent network errors in the request but 0 can't be used
27542                // as the error code because Driver treats 0 as the no error code
27543                errCode = TCAPI_CONN_ERROR;
27544                errDiag = "Aborted, offline, or invalid CORS endpoint";
27545            }
27546            else {
27547                errCode = result.err;
27548                errDiag = result.xhr.responseText;
27549            }
27550        }
27551        else {
27552            errCode = TCAPI_DEP_ERROR;
27553            errDiag = result.err;
27554        }
27555
27556        TCAPI_SetErrorInfoManually(errCode, errMesg, errDiag);
27557    }
27558    else {
27559        WriteToDebug("Unexpected result object, `result.err` null?");
27560    }
27561}
27562
27563// this is just here to make it possible to mock the launch URL
27564// for testing purposes
27565//
27566function TCAPI_GetLaunchUrl () {
27567    WriteToDebug("In TCAPI_GetLaunchUrl");
27568    return location.href;
27569}
27570
27571function TCAPI_Initialize () {
27572    WriteToDebug("In TCAPI_Initialize");
27573
27574    tcapi_cache = {
27575        totalPrevDuration: null,
27576        statementQueue: []
27577    };
27578
27579    TinCan.prototype.log =
27580    TinCan.LRS.prototype.log = function (msg, src) {
27581        src = src || this.LOG_SRC || "TinCan";
27582        WriteToDebug("TinCan." + src + ": " + msg);
27583    };
27584
27585    try {
27586        tincan = new TinCan (
27587            {
27588                url: TCAPI_GetLaunchUrl(),
27589                recordStores: TC_RECORD_STORES,
27590                activity: {
27591                    id: TC_COURSE_ID,
27592                    definition: {
27593                        name: TC_COURSE_NAME,
27594                        description: TC_COURSE_DESC
27595                    }
27596                }
27597            }
27598        );
27599    } catch (ex) {
27600        WriteToDebug("TCAPI_Initialize - TinCan construction failed: " + JSON.stringify(ex));
27601        return;
27602    }
27603
27604    if (tincan.recordStores.length === 0) {
27605        WriteToDebug("TCAPI_Initialize - resulted in no LRS: DATA CANNOT BE STORED");
27606        return;
27607    }
27608
27609    //See if we've saved the cumulative time previously
27610    WriteToDebug("TCAPI_Initialize - fetching cumulative time from state: " + TCAPI_STATE_TOTAL_TIME);
27611    tincan.getState(
27612        TCAPI_STATE_TOTAL_TIME,
27613        {
27614            callback: function (err, state) {
27615                WriteToDebug("TCAPI_Initialize - getState callback");
27616                var contents;
27617                if (err !== null) {
27618                    WriteToDebug("TCAPI_Initialize - getState callback: " + err.responseText + " (" + err.status + ")");
27619                    return;
27620                }
27621
27622                WriteToDebug("TCAPI_Initialize - getState callback - state: " + state);
27623                if (state !== null && state.contents !== null && state.contents.match(/^\d+$/)) {
27624                    tcapi_cache.totalPrevDuration = Number(state.contents);
27625                }
27626                else {
27627                    tcapi_cache.totalPrevDuration = 0;
27628                }
27629            }
27630        }
27631    );
27632
27633    TCAPI_STATUS = TCAPI_INIT_VERB;
27634    TCAPI_IN_PROGRESS = true;
27635
27636    // Record an initial launch statement
27637    WriteToDebug("TCAPI_Initialize - record initial launch statement");
27638    tincan.sendStatement(
27639        {
27640            verb: TCAPI_INIT_VERB,
27641            inProgress: TCAPI_IN_PROGRESS
27642        },
27643        function (results, statement) {
27644            if (results[0].err !== null) {
27645                WriteToDebug("TCAPI_Initialize - record initial launch statement - err: " + results[0].err.responseText + " (" + results[0].err.status + ")");
27646                return;
27647            }
27648
27649            WriteToDebug("TCAPI_Initialize - record initial launch statement success: " + statement.id);
27650        }
27651    );
27652
27653    InitializeExecuted(true, "");
27654
27655    return true;
27656}
27657
27658//
27659// setting state in IE7 was throwing an "Operation Aborted" message for no apparent
27660// reason after the state was already set, so just wrap it with a try/catch
27661//
27662function _TCAPI_SetStateSafe (key, value) {
27663    var result;
27664
27665    try {
27666        result = tincan.setState(key, value);
27667    }
27668    catch (ex) {
27669        //
27670        // this exception is assumed to be the one from IE7, errors should come back
27671        // from TinCanJS via the result object and in particular the `err` property
27672        //
27673        WriteToDebug("In _TCAPI_SetStateSafe - caught exception from setState: " + ex.message);
27674    }
27675
27676    if (result && result.err !== null) {
27677        _TCAPI_HandleRequestErrorResult(result, "Failed to save state (" + key + ")");
27678
27679        return false;
27680    }
27681
27682    return true;
27683}
27684
27685function TCAPI_GetStudentID () {
27686    WriteToDebug("In TCAPI_GetStudentID");
27687
27688    if (tincan.actor.mbox !== null) {
27689        return tincan.actor.mbox;
27690    }
27691    if (tincan.actor.mbox_sha1sum !== null) {
27692        return tincan.actor.mbox_sha1sum;
27693    }
27694    if (tincan.actor.openid !== null) {
27695        return tincan.actor.openid;
27696    }
27697    if (tincan.actor.account !== null) {
27698        //
27699        // it was discussed about whether returning just the account name
27700        // was a proper approach and decided because SD is really only
27701        // supported by LMS launch that the account would normally only
27702        // be used with the launching system's account and that in that
27703        // case the homePage isn't strictly necessary for ID determination
27704        //
27705        // more advanced use cases should avoid using this method if they
27706        // require the homePage as well
27707        //
27708        return tincan.actor.account.name;
27709    }
27710    return null;
27711}
27712
27713function TCAPI_GetStudentName () {
27714    WriteToDebug("In TCAPI_GetStudentName");
27715
27716    return tincan.actor !== null ? tincan.actor.toString() : "";
27717}
27718
27719function TCAPI_GetBookmark () {
27720    WriteToDebug("In TCAPI_GetBookmark");
27721    var bookmark = "",
27722        getStateResult = tincan.getState(TCAPI_STATE_BOOKMARK);
27723
27724    if (getStateResult.state !== null) {
27725        bookmark = getStateResult.state.contents;
27726    }
27727
27728    return bookmark;
27729}
27730
27731function TCAPI_SetBookmark (value, name) {
27732    WriteToDebug("In TCAPI_SetBookmark - value: " + value + ", name: " + name);
27733    var stateResult;
27734
27735    TCAPI_ClearErrorInfo();
27736
27737    stateResult = _TCAPI_SetStateSafe(TCAPI_STATE_BOOKMARK, value);
27738    if (! stateResult) {
27739        return false;
27740    }
27741
27742    WriteToDebug("In TCAPI_SetBookmark - sending statement: " + value);
27743    tincan.sendStatement(
27744        {
27745            verb: TCAPI_VERB_EXPERIENCED,
27746            object: {
27747                id: tincan.activity.id + "/" + value,
27748                definition: {
27749                    name: {
27750                        "en-US": ((name !== undefined && name !== "") ? name : value)
27751                    }
27752                }
27753            },
27754            context: {
27755                contextActivities: {
27756                    parent: tincan.activity
27757                }
27758            }
27759        },
27760        function (results, statement) {
27761            if (results[0].err !== null) {
27762                WriteToDebug("TCAPI_SetBookmark - sending statement: " + value + " - err: " + results[0].err.responseText + " (" + results[0].err.status + ")");
27763                return;
27764            }
27765
27766            WriteToDebug("TCAPI_SetBookmark - sending statement success: " + value + " - id: " + statement.id);
27767        }
27768    );
27769
27770    return true;
27771}
27772
27773function TCAPI_GetDataChunk () {
27774    WriteToDebug("In TCAPI_GetDataChunk");
27775    var data = "",
27776        getStateResult = tincan.getState(TCAPI_STATE_SUSPEND_DATA);
27777
27778    if (getStateResult.state !== null) {
27779        data = getStateResult.state.contents;
27780    }
27781
27782    return data;
27783}
27784
27785function TCAPI_SetDataChunk (value) {
27786    WriteToDebug("In TCAPI_SetDataChunk");
27787
27788    TCAPI_ClearErrorInfo();
27789
27790    return _TCAPI_SetStateSafe(TCAPI_STATE_SUSPEND_DATA, value);
27791}
27792
27793function TCAPI_CommitData () {
27794    WriteToDebug("In TCAPI_CommitData - TCAPI_STATUS:" + TCAPI_STATUS);
27795    WriteToDebug("In TCAPI_CommitData - TCAPI_UPDATES_PENDING: " + TCAPI_UPDATES_PENDING);
27796
27797    var stmt,
27798        requestResults,
27799        result,
27800        statementAdded = false;
27801
27802    TCAPI_ClearErrorInfo();
27803
27804    if (TCAPI_UPDATES_PENDING) {
27805        stmt = {
27806            verb: TCAPI_STATUS,
27807            inProgress: TCAPI_IN_PROGRESS,
27808            result: {}
27809        };
27810
27811        if (TCAPI_COMPLETION_STATUS !== '' || ! TCAPI_IN_PROGRESS) {
27812            // report the duration in the ISO8601 format that SCORM provided
27813            stmt.result.duration = ConvertMilliSecondsIntoSCORM2004Time(
27814                GetSessionAccumulatedTime() + TCAPI_GetPreviouslyAccumulatedTime()
27815            );
27816        }
27817
27818        if (TCAPI_COMPLETION_STATUS !== '') {
27819            stmt.result.completion = true;
27820        }
27821
27822        if (TCAPI_SATISFACTION_STATUS !== null) {
27823            stmt.result.success = TCAPI_SATISFACTION_STATUS;
27824        }
27825
27826        if (typeof TCAPI_SCORE.raw !== "undefined") {
27827            stmt.result.score = TCAPI_SCORE;
27828        }
27829
27830        tcapi_cache.statementQueue.push(stmt);
27831
27832        statementAdded = true;
27833
27834        // reset the pending updates flag
27835        TCAPI_UPDATES_PENDING = false;
27836    }
27837
27838    if (tcapi_cache.statementQueue.length > 0) {
27839        requestResults = tincan.sendStatements(
27840            tcapi_cache.statementQueue
27841        );
27842
27843        if (typeof requestResults.results !== "undefined" && requestResults.results.length > 0) {
27844            //
27845            // we can only count on caring about the first result because
27846            // this supports multiple LRS send, but the first is the one
27847            // the package was configured with at launch
27848            //
27849            result = requestResults.results[0];
27850
27851            if (result.err !== null) {
27852                _TCAPI_HandleRequestErrorResult(result, "Failed to commit data: statements");
27853
27854                //
27855                // if there were updates above, then we added a statement to the queue, but since it
27856                // couldn't be sent we need to remove it from the queue and restore the fact that there
27857                // are updates pending, which also leaves the other statements that might have been queued
27858                // (via interactions for instance) in place for the next commit
27859                //
27860                if (statementAdded) {
27861                    tcapi_cache.statementQueue.pop();
27862
27863                    TCAPI_UPDATES_PENDING = true;
27864                }
27865
27866                return false;
27867            }
27868        }
27869
27870        tcapi_cache.statementQueue = [];
27871    }
27872
27873    return true;
27874}
27875
27876function TCAPI_Finish (exitType, statusWasSet) {
27877    WriteToDebug("In TCAPI_Finish - exitType: " + exitType);
27878
27879    TCAPI_ClearErrorInfo();
27880
27881    if (exitType === EXIT_TYPE_SUSPEND) {
27882        //
27883        // _TCAPI_SetStateSafe sets the error condition and can return `false` when it fails
27884        // but we'd rather have a chance to do CommitData than to return based on a failure
27885        // to capture the accumulated time. The error will be cleared immediately, but at least
27886        // the debug logs will have been captured and if the State request failed there is
27887        // a likely chance that so will the other(s)
27888        //
27889        _TCAPI_SetStateSafe(
27890            TCAPI_STATE_TOTAL_TIME,
27891            TCAPI_GetPreviouslyAccumulatedTime() + GetSessionAccumulatedTime()
27892        );
27893        TCAPI_SetSuspended();
27894    }
27895
27896    TCAPI_CommitData();
27897
27898    return true;
27899}
27900
27901/*****************************
27902* Preferences
27903*****************************/
27904
27905function TCAPI_GetAudioPlayPreference () {
27906    WriteToDebug("In TCAPI_GetAudioPlayPreference");
27907    var intTempPreference = 0,
27908        getStateResult;
27909
27910    TCAPI_ClearErrorInfo();
27911
27912    getStateResult = tincan.getState("cmi.student_preference.audio");
27913    if (getStateResult.state !== null) {
27914        intTempPreference = getStateResult.state.contents;
27915    }
27916
27917    intTempPreference = parseInt(intTempPreference, 10);
27918    WriteToDebug("intTempPreference=" + intTempPreference);
27919
27920    if (intTempPreference > 0) {
27921        WriteToDebug("Returning On");
27922        return PREFERENCE_ON;
27923    }
27924    else if (intTempPreference == 0) {
27925        WriteToDebug("Returning Default");
27926        return PREFERENCE_DEFAULT;
27927    }
27928    else if (intTempPreference < 0) {
27929        WriteToDebug("returning Off");
27930        return PREFERENCE_OFF;
27931    }
27932
27933    WriteToDebug("Error: Invalid preference");
27934    TCAPI_SetErrorInfoManually(
27935        TCAPI_ERROR_INVALID_PREFERENCE,
27936        "Invalid audio preference received from LMS",
27937        "intTempPreference=" + intTempPreference
27938    );
27939    return null;
27940}
27941
27942function TCAPI_GetAudioVolumePreference () {
27943    WriteToDebug("In TCAPI_GetAudioVollumePreference");
27944    var intTempPreference = 100,
27945        getStateResult;
27946
27947    TCAPI_ClearErrorInfo();
27948
27949    getStateResult = tincan.getState("cmi.student_preference.audio");
27950    if (getStateResult.state !== null) {
27951        intTempPreference = getStateResult.state.contents;
27952    }
27953
27954    WriteToDebug("intTempPreference=" + intTempPreference);
27955    intTempPreference = parseInt(intTempPreference, 10);
27956
27957    if (intTempPreference <= 0) {
27958        WriteToDebug("Setting to 100");
27959        intTempPreference = 100;
27960    }
27961
27962    if (intTempPreference > 100) {
27963        WriteToDebug("ERROR: invalid preference");
27964        TCAPI_SetErrorInfoManually(
27965            TCAPI_ERROR_INVALID_PREFERENCE,
27966            "Invalid audio preference received from LMS",
27967            "intTempPreference=" + intTempPreference
27968        );
27969        return null;
27970    }
27971
27972    WriteToDebug("Returning " + intTempPreference);
27973    return intTempPreference;
27974}
27975
27976function TCAPI_SetAudioPreference (PlayPreference, intPercentOfMaxVolume) {
27977    WriteToDebug("In TCAPI_SetAudioPreference PlayPreference=" + PlayPreference + ", intPercentOfMaxVolume=" + intPercentOfMaxVolume);
27978
27979    TCAPI_ClearErrorInfo();
27980
27981    if (PlayPreference == PREFERENCE_OFF) {
27982        WriteToDebug("Setting percent to -1 - OFF");
27983        intPercentOfMaxVolume = -1;
27984    }
27985
27986    return _TCAPI_SetStateSafe("cmi.student_preference.audio", intPercentOfMaxVolume);
27987}
27988
27989function TCAPI_SetLanguagePreference (strLanguage) {
27990    WriteToDebug("In TCAPI_SetLanguagePreference strLanguage=" + strLanguage);
27991    TCAPI_ClearErrorInfo();
27992    return _TCAPI_SetStateSafe("cmi.student_preference.language", strLanguage);
27993}
27994
27995function TCAPI_GetLanguagePreference () {
27996    WriteToDebug("In TCAPI_GetLanguagePreference");
27997    var pref,
27998        getStateResult;
27999
28000    TCAPI_ClearErrorInfo();
28001    getStateResult = tincan.getState("cmi.student_preference.language");
28002    if (getStateResult.state !== null) {
28003        pref = getStateResult.state.contents;
28004    }
28005
28006    return pref;
28007}
28008
28009function TCAPI_SetSpeedPreference (intPercentOfMax) {
28010    WriteToDebug("In TCAPI_SetSpeedPreference intPercentOfMax=" + intPercentOfMax);
28011    var intTCAPISpeed;  //TCAPI's scale is -100 to +100, our range is 1 to 100
28012
28013    TCAPI_ClearErrorInfo();
28014
28015    intTCAPISpeed = (intPercentOfMax * 2) - 100;
28016
28017    WriteToDebug("intTCAPISpeed=" + intTCAPISpeed);
28018    return _TCAPI_SetStateSafe("cmi.student_preference.speed", intTCAPISpeed);
28019}
28020
28021function TCAPI_GetSpeedPreference () {
28022    WriteToDebug("In TCAPI_GetSpeedPreference");
28023    var intTCAPISpeed = 100,
28024        intPercentOfMax,
28025        getStateResult
28026    ;
28027
28028    TCAPI_ClearErrorInfo();
28029
28030    getStateResult = tincan.getState("cmi.student_preference.speed");
28031    if (getStateResult.state !== null) {
28032        intTCAPISpeed = getStateResult.state.contents;
28033    }
28034    WriteToDebug("intTCAPISpeed=" + intTCAPISpeed);
28035
28036    if ( ! ValidInteger(intTCAPISpeed) ) {
28037        WriteToDebug("ERROR - invalid integer");
28038        TCAPI_SetErrorInfoManually(
28039            TCAPI_ERROR_INVALID_SPEED,
28040            "Invalid speed preference received from LMS - not an integer",
28041            "intTCAPISpeed=" + intTCAPISpeed
28042        );
28043        return null;
28044    }
28045
28046    intTCAPISpeed = parseInt(intTCAPISpeed, 10);
28047
28048    if (intTCAPISpeed < -100 || intTCAPISpeed > 100) {
28049        WriteToDebug("ERROR - out of range");
28050        TCAPI_SetErrorInfoManually(
28051            TCAPI_ERROR_INVALID_SPEED,
28052            "Invalid speed preference received from LMS - out of range",
28053            "intTCAPISpeed=" + intTCAPISpeed
28054        );
28055        return null;
28056    }
28057
28058    intPercentOfMax = (intTCAPISpeed + 100) / 2;
28059    intPercentOfMax = parseInt(intPercentOfMax, 10);
28060
28061    WriteToDebug("Returning " + intPercentOfMax);
28062    return intPercentOfMax;
28063}
28064
28065function TCAPI_SetTextPreference (intPreference) {
28066    WriteToDebug("In TCAPI_SetTextPreference intPreference=" + intPreference);
28067    TCAPI_ClearErrorInfo();
28068    return _TCAPI_SetStateSafe("cmi.student_preference.text", intPreference);
28069}
28070
28071function TCAPI_GetTextPreference () {
28072    WriteToDebug("In TCAPI_GetTextPreference");
28073    var intTempPreference = 0,
28074        getStateResult;
28075
28076    TCAPI_ClearErrorInfo();
28077
28078    getStateResult = tincan.getState("cmi.student_preference.text");
28079    if (getStateResult.state !== null) {
28080        intTempPreference = getStateResult.state.contents;
28081    }
28082
28083    intTempPreference = parseInt(intTempPreference, 10);
28084    WriteToDebug("intTempPreference=" + intTempPreference);
28085
28086    if (intTempPreference > 0) {
28087        WriteToDebug("Returning On");
28088        return PREFERENCE_ON;
28089    }
28090    else if (intTempPreference == 0 || intTempPreference == "") {
28091        WriteToDebug("Returning Default");
28092        return PREFERENCE_DEFAULT;
28093    }
28094    else if (intTempPreference < 0) {
28095        WriteToDebug("returning Off");
28096        return PREFERENCE_OFF;
28097    }
28098
28099    WriteToDebug("Error: Invalid preference");
28100    TCAPI_SetErrorInfoManually(
28101        TCAPI_ERROR_INVALID_PREFERENCE,
28102        "Invalid text preference received from LMS",
28103        "intTempPreference=" + intTempPreference
28104    );
28105    return null;
28106}
28107
28108/*****************************
28109*  Time management
28110*****************************/
28111
28112//
28113// returns number of milliseconds duration of all launches previous
28114// to this session
28115//
28116function TCAPI_GetPreviouslyAccumulatedTime () {
28117    WriteToDebug("In TCAPI_GetPreviouslyAccumulatedTime");
28118    var data = 0,
28119        getStateResult;
28120
28121    WriteToDebug("In TCAPI_GetPreviouslyAccumulatedTime - cached: " + tcapi_cache.totalPrevDuration);
28122    if (tcapi_cache.totalPrevDuration === null) {
28123        getStateResult = tincan.getState(TCAPI_STATE_TOTAL_TIME);
28124        if (getStateResult.state !== null) {
28125            data = Number(getStateResult.state.contents);
28126        }
28127        tcapi_cache.totalPrevDuration = (data === NaN ) ? 0 : data;
28128    }
28129
28130    return tcapi_cache.totalPrevDuration;
28131}
28132
28133//
28134// SaveTime is used to track session time which we don't need
28135// in TC because we are creating our own total durations
28136//
28137function TCAPI_SaveTime (intMilliSeconds) {
28138    WriteToDebug("In TCAPI_SaveTime");
28139    return true;
28140}
28141
28142function TCAPI_GetMaxTimeAllowed () {
28143    WriteToDebug("In TCAPI_GetMaxTimeAllowed");
28144    return null;
28145}
28146
28147/*****************************
28148*  Scoring
28149*****************************/
28150
28151function TCAPI_SetScore (intScore, intMaxScore, intMinScore) {
28152    WriteToDebug("In TCAPI_SetScore intScore=" + intScore + ", intMaxScore=" + intMaxScore + ", intMinScore=" + intMinScore);
28153
28154    TCAPI_ClearErrorInfo();
28155
28156    TCAPI_SCORE["raw"] = intScore;
28157    TCAPI_SCORE["max"] = intMaxScore;
28158    TCAPI_SCORE["min"] = intMinScore;
28159
28160    WriteToDebug("Returning " + TCAPI_SCORE);
28161
28162    TCAPI_UPDATES_PENDING = true;
28163
28164    return true;
28165}
28166
28167function TCAPI_GetScore () {
28168    WriteToDebug("In TCAPI_GetScore");
28169
28170    TCAPI_ClearErrorInfo();
28171
28172    WriteToDebug("Returning " + TCAPI_SCORE['raw']);
28173    return TCAPI_SCORE['raw'];
28174}
28175
28176function TCAPI_SetPointBasedScore (intScore, intMaxScore, intMinScore) {
28177    WriteToDebug("TCAPI_SetPointBasedScore - TCAPI does not support SetPointBasedScore, falling back to SetScore");
28178    return TCAPI_SetScore(intScore, intMaxScore, intMinScore);
28179}
28180
28181function TCAPI_GetScaledScore (intScore, intMaxScore, intMinScore) {
28182    WriteToDebug("TCAPI_GetScaledScore - TCAPI does not support GetScaledScore, returning false");
28183    return false;
28184}
28185
28186/***********************
28187*    Interactions
28188************************/
28189
28190function TCAPI_RecordInteraction (strID, strResponse, blnCorrect, strCorrectResponse, strDescription, intWeighting, intLatency, strLearningObjectiveID, dtmTime, TCAPIInteractionType, strAlternateResponse, strAlternateCorrectResponse) {
28191    var blnTempResult,
28192        intInteractionIndex,
28193        strResult,
28194        actObj = {},
28195        stmt,
28196        activityName,
28197
28198        //
28199        // All of the processing for this ID is handled in TCAPI_CreateValidIdentifier.
28200        // In that function, the tincan.activity.id is added to strID and then URI encoded.
28201        // To fix backwards compatibility, the TCAPI_DONT_USE_BROKEN_URN_IDS flag should be set to false,
28202        // which will cause "urn:scormdriver:" to be added between the tincan.activity.id
28203        // and the ID that is passed.
28204        //
28205        // We also set the activity definition name to the original ID that is passed (the interactionActivityId
28206        // without the tincan.activity.id on the beginning).
28207        //
28208        // All of these fixes make it so that future interactions use valid IDs without breaking any
28209        // backwards compatibility.
28210        //
28211        interactionActivityId = strID,
28212        interactionActivityType = TCAPI_INTERACTION
28213    ;
28214
28215    TCAPI_ClearErrorInfo();
28216
28217    if (strID.indexOf(tincan.activity.id + "-") == 0) {
28218        activityName = strID.substring(tincan.activity.id.length + 1);
28219    }
28220
28221    if (! TCAPI_DONT_USE_BROKEN_URN_IDS) {
28222        interactionActivityId = tincan.activity.id + "-urn:scormdriver:" + activityName;
28223    }
28224
28225    switch (TCAPIInteractionType) {
28226        case "true-false":
28227            actObj = {
28228                id: interactionActivityId,
28229                definition: {
28230                    description: {'en-US': strDescription},
28231                    type: interactionActivityType,
28232                    interactionType: "true-false",
28233                    name: {"und": activityName}
28234                }
28235            };
28236            break;
28237        case "choice":
28238            actObj = {
28239                id: interactionActivityId,
28240                definition: {
28241                    description: {'en-US': strDescription},
28242                    type: interactionActivityType,
28243                    interactionType: "choice",
28244                    name: {"und": activityName}
28245                }
28246            };
28247            break;
28248        case "fill-in":
28249            actObj = {
28250                id: interactionActivityId,
28251                definition: {
28252                    description: {'en-US': strDescription},
28253                    type: interactionActivityType,
28254                    interactionType: "fill-in",
28255                    name: {"und": activityName}
28256                }
28257            };
28258            break;
28259        case "matching":
28260            actObj = {
28261                id: interactionActivityId,
28262                definition: {
28263                    description: {'en-US': strDescription},
28264                    type: interactionActivityType,
28265                    interactionType: "matching",
28266                    name: {"und": activityName}
28267                    //source: [],
28268                    //target: []
28269                }
28270            };
28271            break;
28272        case "performance":
28273            actObj = {
28274                id: interactionActivityId,
28275                definition: {
28276                    description: {'en-US': strDescription},
28277                    type: interactionActivityType,
28278                    interactionType: "performance",
28279                    name: {"und": activityName}
28280                    //steps: []
28281                }
28282            };
28283            break;
28284        case "sequencing":
28285            actObj = {
28286                id: interactionActivityId,
28287                definition: {
28288                    description: {'en-US': strDescription},
28289                    type: interactionActivityType,
28290                    interactionType: "sequencing",
28291                    name: {"und": activityName}
28292                    //choices: []
28293                }
28294            };
28295            break;
28296        case "likert":
28297            actObj = {
28298                id: interactionActivityId,
28299                definition: {
28300                    description: {'en-US': strDescription},
28301                    type: interactionActivityType,
28302                    interactionType: "likert",
28303                    name: {"und": activityName}
28304                    //scale: []
28305                }
28306            };
28307            break;
28308        case "numeric":
28309            actObj = {
28310                id: interactionActivityId,
28311                definition: {
28312                    description: {'en-US': strDescription},
28313                    type: interactionActivityType,
28314                    interactionType: "numeric",
28315                    name: {"und": activityName}
28316                }
28317            };
28318            break;
28319        case "other":
28320            actObj = {
28321                id: interactionActivityId,
28322                definition: {
28323                    description: {'en-US': strDescription},
28324                    type: interactionActivityType,
28325                    interactionType: "other",
28326                    name: {"und": activityName}
28327                }
28328            };
28329            break;
28330        default:
28331            WriteToDebug("TCAPI_RecordInteraction received an invali
28331d TCPAIInteractionType of " + TCAPIInteractionType);
28332            return false;
28333    }
28334
28335    if (actObj.id !== null) {
28336        //
28337        // this makes it so that "" can never be a correct response, but that's the way
28338        // the other standards in Driver work, and is potentially a reasonable expectation
28339        //
28340        if (strCorrectResponse !== null && strCorrectResponse !== "") {
28341            actObj.definition.correctResponsesPattern = [strCorrectResponse];
28342        }
28343
28344        stmt = {
28345            verb: TCAPI_VERB_ANSWERED,
28346            object: actObj,
28347            context: {
28348                contextActivities: {
28349                    parent: tincan.activity,
28350                    grouping: {
28351                        id: tincan.activity.id + '-' + strLearningObjectiveID
28352                    }
28353                }
28354            }
28355        };
28356
28357        if ((strResponse !== null) || (intLatency !== null && intLatency !== "") || (intWeighting !== null && intWeighting !== "")) {
28358            stmt.result = {};
28359
28360            if (strResponse !== null) {
28361                stmt.result.response = strResponse;
28362                if (blnCorrect === true || blnCorrect === INTERACTION_RESULT_CORRECT) {
28363                    stmt.result.success = true;
28364                }
28365                else if (blnCorrect === false || blnCorrect === "" || blnCorrect === "false" || blnCorrect === INTERACTION_RESULT_WRONG) {
28366                    stmt.result.success = false;
28367                }
28368            }
28369            if (intLatency !== null && intLatency !== "") {
28370                stmt.result.duration = TinCan.Utils.convertMillisecondsToISO8601Duration(intLatency);
28371            }
28372            if (intWeighting !== null && intWeighting !== "") {
28373                stmt.result.extensions = stmt.result.extensions || {};
28374                stmt.result.extensions["http://id.tincanapi.com/extension/cmi-interaction-weighting"] = intWeighting;
28375            }
28376        }
28377
28378        tcapi_cache.statementQueue.push(stmt);
28379    }
28380
28381    return true;
28382}
28383
28384function TCAPI_RecordTrueFalseInteraction (strID, blnResponse, blnCorrect, blnCorrectResponse, strDescription, intWeighting, intLatency, strLearningObjectiveID, dtmTime) {
28385    WriteToDebug(
28386        "In TCAPI_RecordTrueFalseInteraction strID=" + strID + ", blnResponse=" + blnResponse +
28387        ", blnCorrect=" + blnCorrect + ", blnCorrectResponse=" + blnCorrectResponse +
28388        ", strDescription=" + strDescription + ", intWeighting=" + intWeighting + ", intLatency=" + intLatency +
28389        ", strLearningObjectiveID=" + strLearningObjectiveID + ", dtmTime=" + dtmTime
28390    );
28391
28392    var strResponse = null,
28393        strCorrectResponse = null
28394    ;
28395
28396    if (blnResponse === true) {
28397        strResponse = "true";
28398    }
28399    // test for false b/c it could be null in which case we want to leave it as `null`
28400    else if (blnResponse === false) {
28401        strResponse = "false";
28402    }
28403
28404    if (blnCorrectResponse === true) {
28405        strCorrectResponse = "true";
28406    }
28407    // test for false b/c it could be null in which case we want to leave it as `null`
28408    else if (blnCorrectResponse === false) {
28409        strCorrectResponse = "false";
28410    }
28411
28412    return TCAPI_RecordInteraction(
28413        strID,
28414        strResponse,
28415        blnCorrect,
28416        strCorrectResponse,
28417        strDescription,
28418        intWeighting,
28419        intLatency,
28420        strLearningObjectiveID,
28421        dtmTime,
28422        TCAPI_INTERACTION_TYPE_TRUE_FALSE,
28423        strResponse,
28424        strCorrectResponse
28425    );
28426}
28427
28428function TCAPI_RecordMultipleChoiceInteraction (strID, aryResponse, blnCorrect, aryCorrectResponse, strDescription, intWeighting, intLatency, strLearningObjectiveID, dtmTime) {
28429    WriteToDebug(
28430        "In TCAPI_RecordMultipleChoiceInteraction strID=" + strID + ", aryResponse=" + aryResponse +
28431        ", blnCorrect=" + blnCorrect + ", aryCorrectResponse=" + aryCorrectResponse +
28432        ", strDescription=" + strDescription + ", intWeighting=" + intWeighting + ", intLatency=" + intLatency +
28433        ", strLearningObjectiveID=" + strLearningObjectiveID + ", dtmTime=" + dtmTime
28434    );
28435
28436    var strResponse = null,
28437        strResponseLong = null,
28438        strCorrectResponse = "",
28439        strCorrectResponseLong = ""
28440    ;
28441
28442    if (aryResponse !== null) {
28443        strResponse = "";
28444        strResponseLong = "";
28445
28446        for (var i = 0; i < aryResponse.length; i++) {
28447            if (strResponse.length > 0) { strResponse += "[,]"; }
28448            if (strResponseLong.length > 0) { strResponseLong += "[,]"; }
28449
28450            strResponse += aryResponse[i].Short;
28451            strResponseLong += aryResponse[i].Long;
28452        }
28453    }
28454
28455    for (var i = 0; i < aryCorrectResponse.length; i++) {
28456        if (strCorrectResponse.length > 0) { strCorrectResponse += "[,]"; }
28457        if (strCorrectResponseLong.length > 0) { strCorrectResponseLong += "[,]"; }
28458
28459        strCorrectResponse += aryCorrectResponse[i].Short;
28460        strCorrectResponseLong += aryCorrectResponse[i].Long;
28461    }
28462
28463    return TCAPI_RecordInteraction(
28464        strID,
28465        strResponseLong,
28466        blnCorrect,
28467        strCorrectResponseLong,
28468        strDescription,
28469        intWeighting,
28470        intLatency,
28471        strLearningObjectiveID,
28472        dtmTime,
28473        TCAPI_INTERACTION_TYPE_CHOICE,
28474        strResponse,
28475        strCorrectResponse
28476    );
28477}
28478
28479function TCAPI_RecordFillInInteraction (strID, strResponse, blnCorrect, strCorrectResponse, strDescription, intWeighting, intLatency, strLearningObjectiveID, dtmTime) {
28480    WriteToDebug(
28481        "In TCAPI_RecordFillInInteraction strID=" + strID + ", strResponse=" + strResponse +
28482        ", blnCorrect=" + blnCorrect + ", strCorrectResponse=" + strCorrectResponse +
28483        ", strDescription=" + strDescription + ", intWeighting=" + intWeighting + ", intLatency=" + intLatency +
28484        ", strLearningObjectiveID=" + strLearningObjectiveID + ", dtmTime=" + dtmTime
28485    );
28486
28487    if (strResponse !== null) {
28488        strResponse = new String(strResponse);
28489
28490        if (strResponse.length > 255) {
28491            strResponse = strResponse.substr(0, 255);
28492        }
28493    }
28494
28495    if (strCorrectResponse !== null) {
28496        strCorrectResponse = new String(strCorrectResponse);
28497
28498        if (strCorrectResponse.length > 255) {
28499            strCorrectResponse = strCorrectResponse.substr(0, 255);
28500        }
28501    }
28502
28503    return TCAPI_RecordInteraction(
28504        strID,
28505        strResponse,
28506        blnCorrect,
28507        strCorrectResponse,
28508        strDescription,
28509        intWeighting,
28510        intLatency,
28511        strLearningObjectiveID,
28512        dtmTime,
28513        TCAPI_INTERACTION_TYPE_FILL_IN,
28514        strResponse,
28515        strCorrectResponse
28516    );
28517}
28518
28519function TCAPI_RecordMatchingInteraction (strID, aryResponse, blnCorrect, aryCorrectResponse, strDescription, intWeighting, intLatency, strLearningObjectiveID, dtmTime) {
28520    WriteToDebug(
28521        "In TCAPI_RecordMatchingInteraction strID=" + strID + ", aryResponse=" + aryResponse +
28522        ", blnCorrect=" + blnCorrect + ", aryCorrectResponse=" + aryCorrectResponse +
28523        ", strDescription=" + strDescription + ", intWeighting=" + intWeighting + ", intLatency=" + intLatency +
28524        ", strLearningObjectiveID=" + strLearningObjectiveID + ", dtmTime=" + dtmTime
28525    );
28526
28527    var strResponse = null,
28528        strResponseLong = null,
28529        strCorrectResponse = "",
28530        strCorrectResponseLong = ""
28531    ;
28532
28533    if (aryResponse !== null) {
28534        strResponse = "";
28535        strResponseLong = "";
28536
28537        for (var i = 0; i < aryResponse.length; i++) {
28538            if (strResponse.length > 0) { strResponse += "[,]"; }
28539            if (strResponseLong.length > 0) { strResponseLong += "[,]"; }
28540
28541            strResponse += aryResponse[i].Source.Short + "[.]" + aryResponse[i].Target.Short;
28542            strResponseLong += aryResponse[i].Source.Long + "[.]" + aryResponse[i].Target.Long;
28543        }
28544    }
28545
28546    for (var i = 0; i < aryCorrectResponse.length; i++) {
28547        if (strCorrectResponse.length > 0) { strCorrectResponse += "[,]"; }
28548        if (strCorrectResponseLong.length > 0) { strCorrectResponseLong += "[,]"; }
28549
28550        strCorrectResponse += aryCorrectResponse[i].Source.Short + "[.]" + aryCorrectResponse[i].Target.Short;
28551        strCorrectResponseLong += aryCorrectResponse[i].Source.Long + "[.]" + aryCorrectResponse[i].Target.Long;
28552    }
28553
28554    return TCAPI_RecordInteraction(
28555        strID,
28556        strResponseLong,
28557        blnCorrect,
28558        strCorrectResponseLong,
28559        strDescription,
28560        intWeighting,
28561        intLatency,
28562        strLearningObjectiveID,
28563        dtmTime,
28564        TCAPI_INTERACTION_TYPE_MATCHING,
28565        strResponse,
28566        strCorrectResponse
28567    );
28568}
28569
28570function TCAPI_RecordPerformanceInteraction (strID, strResponse, blnCorrect, strCorrectResponse, strDescription, intWeighting, intLatency, strLearningObjectiveID, dtmTime) {
28571    WriteToDebug(
28572        "In TCAPI_RecordPerformanceInteraction strID=" + strID + ", strResponse=" + strResponse +
28573        ", blnCorrect=" + blnCorrect + ", strCorrectResponse=" + strCorrectResponse +
28574        ", strDescription=" + strDescription + ", intWeighting=" + intWeighting + ", intLatency=" + intLatency +
28575        ", strLearningObjectiveID=" + strLearningObjectiveID + ", dtmTime=" + dtmTime
28576    );
28577
28578    if (strResponse !== null) {
28579        strResponse = new String(strResponse);
28580        if (strResponse.length > 255) {
28581            strResponse = strResponse.substr(0, 255);
28582        }
28583    }
28584
28585    if (strCorrectResponse !== null) {
28586        strCorrectResponse = new String(strCorrectResponse);
28587        if (strCorrectResponse.length > 255) {
28588            strCorrectResponse = strCorrectResponse.substr(0, 255);
28589        }
28590    }
28591
28592    return TCAPI_RecordInteraction(
28593        strID,
28594        strResponse,
28595        blnCorrect,
28596        strCorrectResponse,
28597        strDescription,
28598        intWeighting,
28599        intLatency,
28600        strLearningObjectiveID,
28601        dtmTime,
28602        TCAPI_INTERACTION_TYPE_PERFORMANCE,
28603        strResponse,
28604        strCorrectResponse
28605    );
28606}
28607
28608function TCAPI_RecordSequencingInteraction (strID, aryResponse, blnCorrect, aryCorrectResponse, strDescription, intWeighting, intLatency, strLearningObjectiveID, dtmTime) {
28609    WriteToDebug(
28610        "In TCAPI_RecordSequencingInteraction strID=" + strID + ", aryResponse=" + aryResponse +
28611        ", blnCorrect=" + blnCorrect + ", aryCorrectResponse=" + aryCorrectResponse +
28612        ", strDescription=" + strDescription + ", intWeighting=" + intWeighting + ", intLatency=" + intLatency +
28613        ", strLearningObjectiveID=" + strLearningObjectiveID + ", dtmTime=" + dtmTime
28614    );
28615
28616    var strResponse = null,
28617        strResponseLong = null,
28618        strCorrectResponse = "",
28619        strCorrectResponseLong = ""
28620    ;
28621
28622    if (aryResponse !== null) {
28623        strResponse = "";
28624        strResponseLong = "";
28625
28626        for (var i = 0; i < aryResponse.length; i++) {
28627            if (strResponse.length > 0) { strResponse += "[,]"; }
28628            if (strResponseLong.length > 0) { strResponseLong += "[,]"; }
28629
28630            strResponse += aryResponse[i].Short;
28631            strResponseLong += aryResponse[i].Long;
28632        }
28633    }
28634
28635    for (var i = 0; i < aryCorrectResponse.length; i++) {
28636        if (strCorrectResponse.length > 0) { strCorrectResponse += "[,]"; }
28637        if (strCorrectResponseLong.length > 0) { strCorrectResponseLong += "[,]"; }
28638
28639        strCorrectResponse += aryCorrectResponse[i].Short;
28640        strCorrectResponseLong += aryCorrectResponse[i].Long;
28641    }
28642
28643    return TCAPI_RecordInteraction(
28644        strID,
28645        strResponseLong,
28646        blnCorrect,
28647        strCorrectResponseLong,
28648        strDescription,
28649        intWeighting,
28650        intLatency,
28651        strLearningObjectiveID,
28652        dtmTime,
28653        TCAPI_INTERACTION_TYPE_SEQUENCING,
28654        strResponse,
28655        strCorrectResponse
28656    );
28657}
28658
28659function TCAPI_RecordLikertInteraction (strID, response, blnCorrect, correctResponse, strDescription, intWeighting, intLatency, strLearningObjectiveID, dtmTime) {
28660    WriteToDebug(
28661        "In TCAPI_RecordLikertInteraction strID=" + strID + ", response=" + response +
28662        ", blnCorrect=" + blnCorrect + ", correctResponse=" + correctResponse +
28663        ", strDescription=" + strDescription + ", intWeighting=" + intWeighting + ", intLatency=" + intLatency +
28664        ", strLearningObjectiveID=" + strLearningObjectiveID + ", dtmTime=" + dtmTime
28665    );
28666
28667    var strResponse = null,
28668        strResponseLong = null,
28669        strCorrectResponse = null,
28670        strCorrectResponseLong = null
28671    ;
28672
28673    if (response !== null) {
28674        strResponse = response.Short;
28675        strResponseLong = response.Long;
28676    }
28677    if (correctResponse !== null) {
28678        strCorrectResponse = correctResponse.Short;
28679        strCorrectResponseLong = correctResponse.Long;
28680    }
28681
28682    return TCAPI_RecordInteraction(
28683        strID,
28684        strResponseLong,
28685        blnCorrect,
28686        strCorrectResponseLong,
28687        strDescription,
28688        intWeighting,
28689        intLatency,
28690        strLearningObjectiveID,
28691        dtmTime,
28692        TCAPI_INTERACTION_TYPE_LIKERT,
28693        strResponse,
28694        strCorrectResponse
28695    );
28696}
28697
28698function TCAPI_RecordNumericInteraction(strID, strResponse, blnCorrect, strCorrectResponse, strDescription, intWeighting, intLatency, strLearningObjectiveID, dtmTime) {
28699    WriteToDebug(
28700        "In TCAPI_RecordNumericInteraction strID=" + strID + ", strResponse=" + strResponse +
28701        ", blnCorrect=" + blnCorrect + ", strCorrectResponse=" + strCorrectResponse +
28702        ", strDescription=" + strDescription + ", intWeighting=" + intWeighting + ", intLatency=" + intLatency +
28703        ", strLearningObjectiveID=" + strLearningObjectiveID + ", dtmTime=" + dtmTime
28704    );
28705
28706    if (typeof strCorrectResponse !== "undefined" && strCorrectResponse !== null) {
28707        // Tin Can can handle numeric ranges or decimals. As long as we received one of those we are ok to continue
28708        if (! IsValidDecimalRange(strCorrectResponse) && ! IsValidDecimal(strCorrectResponse)) {
28709            WriteToDebug("Returning False -  TCAPI_RecordNumericInteraction received invalid correct response (not a decimal or range), strCorrectResponse=" + strCorrectResponse);
28710            return false;
28711        }
28712    }
28713
28714    return TCAPI_RecordInteraction(
28715        strID,
28716        strResponse,
28717        blnCorrect,
28718        strCorrectResponse,
28719        strDescription,
28720        intWeighting,
28721        intLatency,
28722        strLearningObjectiveID,
28723        dtmTime,
28724        TCAPI_INTERACTION_TYPE_NUMERIC,
28725        strResponse,
28726        strCorrectResponse
28727    );
28728}
28729
28730function TCAPI_GetEntryMode () {
28731    WriteToDebug("In TCAPI_GetEntryMode");
28732    /*
28733    var strEntry;
28734
28735    TCAPI_ClearErrorInfo();
28736
28737    strEntry = TCAPI_CallLMSGetValue("cmi.core.entry");
28738
28739    WriteToDebug("strEntry=" + strEntry);
28740
28741    if (strEntry == TCAPI_ENTRY_ABINITIO) {
28742        WriteToDebug("Returning first time");
28743        return ENTRY_FIRST_TIME;
28744    }
28745    else if (strEntry == TCAPI_ENTRY_RESUME) {
28746        WriteToDebug("Returning resume");
28747        return ENTRY_RESUME;
28748    }
28749    else if (strEntry == TCAPI_ENTRY_NORMAL) {
28750        WriteToDebug("returning normal");
28751        return ENTRY_REVIEW;
28752    }
28753    else{
28754        WriteToDebug("ERROR - invalide entry mode");
28755        TCAPI_SetErrorInfoManually(TCAPI_ERROR_INVALID_ENTRY,
28756                        "Invalid entry vocab received from LMS",
28757                        "strEntry=" + strEntry);
28758        return null;
28759    }
28760    */
28761    return null;
28762}
28763
28764function TCAPI_GetLessonMode () {
28765    WriteToDebug("In TCAPI_GetLessonMode");
28766    /*
28767    var strLessonMode;
28768
28769    TCAPI_ClearErrorInfo();
28770
28771    strLessonMode = TCAPI_CallLMSGetValue("cmi.core.lesson_mode");
28772
28773    WriteToDebug("strLessonMode=" + strLessonMode);
28774
28775    if (strLessonMode == TCAPI_BROWSE) {
28776        WriteToDebug("Returning browse");
28777        return MODE_BROWSE;
28778    }
28779    else if(strLessonMode == TCAPI_NORMAL) {
28780        WriteToDebug("returning normal");
28781        return MODE_NORMAL;
28782    }
28783    else if(strLessonMode == TCAPI_REVIEW) {
28784        WriteToDebug("Returning Review");
28785        return MODE_REVIEW;
28786    }
28787    else{
28788        WriteToDebug("ERROR - invalid lesson mode");
28789        TCAPI_SetErrorInfoManually(TCAPI_ERROR_INVALID_LESSON_MODE,
28790                                "Invalid lesson_mode vocab received from LMS",
28791                                "strLessonMode=" + strLessonMode);
28792        return null;
28793    }
28794    */
28795    return null;
28796}
28797
28798function TCAPI_GetTakingForCredit () {
28799    WriteToDebug("In TCAPI_GetTakingForCredit");
28800    /*
28801    var strCredit;
28802
28803    TCAPI_ClearErrorInfo();
28804
28805    strCredit = TCAPI_CallLMSGetValue("cmi.core.credit");
28806
28807    WriteToDebug("strCredit=" + strCredit);
28808
28809    if (strCredit == "credit") {
28810        WriteToDebug("Returning true");
28811        return true;
28812    }
28813    else if (strCredit == "no-credit") {
28814        WriteToDebug("Returning false");
28815        return false;
28816    }
28817    else{
28818        WriteToDebug("ERROR - invalid credit");
28819        TCAPI_SetErrorInfoManually(TCAPI_ERROR_INVALID_CREDIT,
28820                                  "Invalid credit vocab received from LMS",
28821                                  "strCredit=" + strCredit);
28822        return null;
28823    }
28824    */
28825    return null;
28826}
28827
28828/*****************************
28829* Objectives
28830*****************************/
28831
28832function TCAPI_SetObjectiveScore (strObjectiveID, intScore, intMaxScore, intMinScore) {
28833    WriteToDebug("In TCAPI_SetObjectiveScore, strObejctiveID=" + strObjectiveID + ", intScore=" + intScore + ", intMaxScore=" + intMaxScore + ", intMinScore=" + intMinScore);
28834
28835    return false;
28836}
28837
28838function TCAPI_SetObjectiveDescription (strObjectiveID, strObjectiveDescription) {
28839    WriteToDebug("In TCAPI_SetObjectiveDescription, strObjectiveDescription=" + strObjectiveDescription);
28840
28841    return false;
28842}
28843
28844function TCAPI_SetObjectiveStatus (strObjectiveID, Lesson_Status) {
28845    WriteToDebug("In TCAPI_SetObjectiveStatus strObjectiveID=" + strObjectiveID + ", Lesson_Status=" + Lesson_Status);
28846
28847    return false;
28848}
28849
28850function TCAPI_GetObjectiveScore (strObjectiveID) {
28851    WriteToDebug("In TCAPI_GetObjectiveScore, strObejctiveID=" + strObjectiveID);
28852
28853    return false;
28854}
28855
28856function TCAPI_GetObjectiveDescription (strObjectiveID) {
28857    WriteToDebug("In TCAPI_GetObjectiveDescription, strObejctiveID=" + strObjectiveID);
28858
28859    return false;
28860}
28861
28862function TCAPI_GetObjectiveStatus (strObjectiveID) {
28863    WriteToDebug("In TCAPI_GetObjectiveStatus, strObejctiveID=" + strObjectiveID);
28864
28865    return false;
28866}
28867
28868/*****************************
28869*  Status Management
28870*****************************/
28871
28872function TCAPI_SetSuspended () {
28873    WriteToDebug("In TCAPI_SetSuspended");
28874
28875    if (TCAPI_IN_PROGRESS) {
28876        TCAPI_IN_PROGRESS = false;
28877        TCAPI_UPDATES_PENDING = true;
28878    }
28879
28880    return true;
28881}
28882
28883function TCAPI_SetFailed () {
28884    WriteToDebug("In TCAPI_SetFailed");
28885
28886    TCAPI_STATUS = TCAPI_VERB_FAILED;
28887    TCAPI_STATUS_CHANGED = true;
28888    TCAPI_SATISFACTION_STATUS = false;
28889    TCAPI_IN_PROGRESS = false;
28890    TCAPI_UPDATES_PENDING = true;
28891
28892    return true;
28893}
28894
28895function TCAPI_SetPassed () {
28896    WriteToDebug("In TCAPI_SetPassed");
28897
28898    TCAPI_STATUS = TCAPI_VERB_PASSED;
28899    TCAPI_STATUS_CHANGED = true;
28900    TCAPI_SATISFACTION_STATUS = true;
28901    TCAPI_IN_PROGRESS = false;
28902    TCAPI_UPDATES_PENDING = true;
28903
28904    return true;
28905}
28906
28907function TCAPI_SetCompleted () {
28908    WriteToDebug("In TCAPI_SetCompleted");
28909
28910    TCAPI_ClearErrorInfo();
28911
28912    if (TCAPI_STATUS === TCAPI_INIT_VERB) {
28913        // only set verb to completed if not already passed or failed
28914        TCAPI_STATUS = TCAPI_VERB_COMPLETED;
28915        TCAPI_STATUS_CHANGED = true;
28916    }
28917    TCAPI_COMPLETION_STATUS = TCAPI_VERB_COMPLETED;
28918    TCAPI_IN_PROGRESS = false;
28919    TCAPI_UPDATES_PENDING = true;
28920
28921    return true;
28922}
28923
28924function TCAPI_ResetStatus () {
28925    WriteToDebug("In TCAPI_ResetStatus");
28926
28927    TCAPI_ClearErrorInfo();
28928
28929    TCAPI_STATUS = TCAPI_INIT_VERB;
28930    TCAPI_STATUS_CHANGED = true;
28931    TCAPI_COMPLETION_STATUS = '';
28932    TCAPI_SATISFACTION_STATUS = null;
28933    TCAPI_IN_PROGRESS = true;
28934    TCAPI_UPDATES_PENDING = true;
28935
28936    return true;
28937}
28938
28939function TCAPI_GetStatus () {
28940    WriteToDebug("In TCAPI_GetStatus");
28941    var strStatus = "";
28942
28943    TCAPI_ClearErrorInfo();
28944
28945    if (TCAPI_STATUS === TCAPI_VERB_COMPLETED) {
28946        strStatus = "completed";
28947    }
28948    else if (TCAPI_STATUS === TCAPI_VERB_ATTEMPTED) {
28949        strStatus = "attempted";
28950    }
28951    else if (TCAPI_STATUS === TCAPI_VERB_PASSED) {
28952        strStatus = "passed";
28953    }
28954    else if (TCAPI_STATUS === TCAPI_VERB_FAILED) {
28955        strStatus = "failed";
28956    }
28957    else {
28958        strStatus = TCAPI_STATUS;
28959    }
28960
28961    WriteToDebug("In TCAPI_GetStatus - strStatus=" + strStatus);
28962    return strStatus;
28963}
28964
28965function TCAPI_CreateValidIdentifier(str) {
28966    return tincan.activity.id + "-" + encodeURIComponent(str);
28967}
28968
28969/*****************************
28970*  Unsupported Methods
28971*
28972* TODO: should these be supported through "state"?
28973*****************************/
28974
28975function TCAPI_SetNavigationRequest (strNavRequest) {
28976    WriteToDebug("TCAPI_GetNavigationRequest - TCAPI does not support navigation requests, returning false");
28977    return false;
28978}
28979
28980function TCAPI_GetNavigationRequest () {
28981    WriteToDebug("TCAPI_GetNavigationRequest - TCAPI does not support navigation requests, returning false");
28982    return false;
28983}
28984
28985function TCAPI_CreateDataBucket (strBucketId, intMinSize, intMaxSize) {
28986    WriteToDebug("TCAPI_CreateDataBucket - TCAPI does not support SSP, returning false");
28987    return false;
28988}
28989
28990function TCAPI_GetDataFromBucket (strBucketId) {
28991    WriteToDebug("TCAPI_GetDataFromBucket - TCAPI does not support SSP, returning empty string");
28992    return "";
28993}
28994
28995function TCAPI_PutDataInBucket (strBucketId, strData, blnAppendToEnd) {
28996    WriteToDebug("TCAPI_PutDataInBucket - TCAPI does not support SSP, returning false");
28997    return false;
28998}
28999
29000function TCAPI_DetectSSPSupport () {
29001    WriteToDebug("TCAPI_DetectSSPSupport - TCAPI does not support SSP, returning false");
29002    return false;
29003}
29004
29005function TCAPI_GetBucketInfo (strBucketId) {
29006    WriteToDebug("TCAPI_GetBucketInfo - TCAPI does not support SSP, returning empty SSPBucketSize");
29007    return new SSPBucketSize(0, 0);
29008}
29009
29010function TCAPI_WriteComment (strComment) {
29011    WriteToDebug("In TCAPI_WriteComment - TCAPI does not support comments");
29012    return false;
29013}
29014
29015function TCAPI_GetComments () {
29016    WriteToDebug("In TCAPI_GetComments - TCAPI does not support comments");
29017    return "";
29018}
29019
29020function TCAPI_GetLMSComments () {
29021    WriteToDebug("In TCAPI_GetLMSComments - TCAPI does not support LMS comments");
29022   return "";
29023}
29024
29025function TCAPI_GetLaunchData () {
29026    WriteToDebug("In TCAPI_GetLaunchData - TCAPI does not support launch data");
29027    return false;
29028}
29029
29030function TCAPI_DisplayMessageOnTimeout () {
29031    TCAPI_ClearErrorInfo();
29032
29033    WriteToDebug("In TCAPI_DisplayMessageOnTimeout - TCAPI does not support MessageOnTimeout");
29034    return false;
29035}
29036
29037function TCAPI_ExitOnTimeout () {
29038    WriteToDebug("In TCAPI_ExitOnTimeout - TCAPI does not support ExitOnTimeout");
29039    return false;
29040}
29041
29042function TCAPI_GetPassingScore () {
29043    WriteToDebug("In TCAPI_GetPassingScore - TCAPI does not support GetPassingScore");
29044    return false;
29045}
29046
29047function TCAPI_GetProgressMeasure () {
29048    WriteToDebug("TCAPI_GetProgressMeasure - TCAPI does not support progress_measure, returning false");
29049    return false;
29050}
29051
29052function TCAPI_SetProgressMeasure () {
29053    WriteToDebug("TCAPI_SetProgressMeasure - TCAPI does not support progress_measure, returning false");
29054    return false;
29055}
29056
29057function TCAPI_GetObjectiveProgressMeasure () {
29058    WriteToDebug("TCAPI_GetObjectiveProgressMeasure - TCAPI does not support progress_measure, returning false");
29059    return false;
29060}
29061
29062function TCAPI_SetObjectiveProgressMeasure () {
29063    WriteToDebug("TCAPI_SetObjectiveProgressMeasure - TCAPI does not support progress_measure, returning false");
29064    return false;
29065}
29066
29067function TCAPI_GetInteractionType(strInteractionID) {
29068    WriteToDebug("TCAPI_GetInteractionType - TCAPI does not support interaction retrieval, returning empty string");
29069    return '';
29070}
29071
29072function TCAPI_GetInteractionTimestamp(strInteractionID) {
29073    WriteToDebug("TCAPI_GetInteractionTimestamp - TCAPI does not support interaction retrieval, returning empty string");
29074    return '';
29075}
29076
29077function TCAPI_GetInteractionCorrectResponses(strInteractionID) {
29078    WriteToDebug("TCAPI_GetInteractionCorrectResponses - TCAPI does not support interaction retrieval, returning empty array");
29079    return [];
29080}
29081
29082function TCAPI_GetInteractionWeighting(strInteractionID) {
29083    WriteToDebug("TCAPI_GetInteractionWeighting - TCAPI does not support interaction retrieval, returning empty string");
29084    return '';
29085}
29086
29087function TCAPI_GetInteractionLearnerResponses(strInteractionID) {
29088    WriteToDebug("TCAPI_GetInteractionLearnerResponses - TCAPI does not support interaction retrieval, returning empty array");
29089    return [];
29090}
29091
29092function TCAPI_GetInteractionResult(strInteractionID) {
29093    WriteToDebug("TCAPI_GetInteractionResult - TCAPI does not support interaction retrieval, returning empty string");
29094    return '';
29095}
29096
29097function TCAPI_GetInteractionLatency(strInteractionID) {
29098    WriteToDebug("TCAPI_GetInteractionDescription - TCAPI does not support interaction retrieval, returning empty string");
29099    return '';
29100}
29101
29102function TCAPI_GetInteractionDescription(strInteractionID) {
29103    WriteToDebug("TCAPI_GetInteractionDescription - TCAPI does not support interaction retrieval, returning empty string");
29104    return '';
29105}
29106
29107/*****************************
29108*  Error Management
29109*****************************/
29110
29111// TODO: global variables used that need to be declared
29112function TCAPI_ClearErrorInfo () {
29113    WriteToDebug("In TCAPI_ClearErrorInfo");
29114
29115    intTCAPIError = TCAPI_NO_ERROR;
29116    strTCAPIErrorString = "";
29117    strTCAPIErrorDiagnostic = "";
29118}
29119
29120function TCAPI_SetErrorInfoManually (intNum, strString, strDiagnostic) {
29121    WriteToDebug("In TCAPI_SetErrorInfoManually");
29122    WriteToDebug("ERROR-Num=" + intNum);
29123    WriteToDebug("      String=" + strString);
29124    WriteToDebug("      Diag=" + strDiagnostic);
29125
29126    intTCAPIError = intNum;
29127    strTCAPIErrorString = strString;
29128    strTCAPIErrorDiagnostic = strDiagnostic;
29129}
29130
29131function TCAPI_GetLastError () {
29132    WriteToDebug("In TCAPI_GetLastError");
29133
29134    if (intTCAPIError === TCAPI_NO_ERROR) {
29135        WriteToDebug("Returning No Error");
29136        return NO_ERROR;
29137    }
29138    else {
29139        WriteToDebug("Returning " + intTCAPIError);
29140        return intTCAPIError;
29141    }
29142}
29143
29144function TCAPI_GetLastErrorDesc () {
29145    WriteToDebug("In TCAPI_GetLastErrorDesc, " + strTCAPIErrorString + "\n" + strTCAPIErrorDiagnostic);
29146    return strTCAPIErrorString + "\n" + strTCAPIErrorDiagnostic;
29147}
29148/* declare other globals we'll use */
29149
29150var CMI5_PENDING_STATUS = {
29151        completion: null,
29152        success: null,
29153        score: null
29154    },
29155    CMI5_COMMITTED_STATUS = {
29156        completion: null,
29157        success: null,
29158        score: null,
29159        launchModes: []
29160    },
29161    CMI5_STATEMENT_QUEUE = [],
29162
29163    //
29164    // cmi5.js will calculate session duration automatically, but SD has the ability
29165    // to let the user manually set the duration of learning so we use it to override
29166    // the automatically handled duration by cmi5.js
29167    //
29168    CMI5_SESSION_DURATION = null,
29169
29170    //
29171    // cmi5 doesn't have the concept of total duration across sessions so we manufacture
29172    // it using the State API
29173    //
29174    CMI5_TOTAL_PREV_DURATION = null,
29175
29176    CMI5_ENTRY_MODE = null,
29177    CMI5_INTERACTIONS = {},
29178    CMI5_SSP_BUCKETS = {},
29179
29180    CMI5_VERB_ID_FAILED = "http://adlnet.gov/expapi/verbs/failed",
29181    CMI5_VERB_ID_PASSED = "http://adlnet.gov/expapi/verbs/passed",
29182    CMI5_VERB_ID_COMPLETED = "http://adlnet.gov/expapi/verbs/completed",
29183
29184    //
29185    // local cache of these values so only need to round trip once, they are updated
29186    // when calling set so no need to re-fetch
29187    //
29188    CMI5_PREF_AUDIO_PLAY = null,
29189    CMI5_PREF_AUDIO_VOLUME = null,
29190    CMI5_PREF_LANGUAGE = null,
29191    CMI5_PREF_SPEED = null,
29192    CMI5_PREF_TEXT = null,
29193
29194    CMI5_INTERACTION_ACTIVITY_TYPE = "http://adlnet.gov/expapi/activities/cmi.interaction",
29195    CMI5_INTERACTION_TYPE_TRUE_FALSE = "true-false",
29196    CMI5_INTERACTION_TYPE_CHOICE = "choice",
29197    CMI5_INTERACTION_TYPE_FILL_IN = "fill-in",
29198    CMI5_INTERACTION_TYPE_LONG_FILL_IN = "long-fill-in",
29199    CMI5_INTERACTION_TYPE_MATCHING = "matching",
29200    CMI5_INTERACTION_TYPE_PERFORMANCE = "performance",
29201    CMI5_INTERACTION_TYPE_SEQUENCING = "sequencing",
29202    CMI5_INTERACTION_TYPE_LIKERT = "likert",
29203    CMI5_INTERACTION_TYPE_NUMERIC = "numeric",
29204
29205    //
29206    // these match their counterparts in the TCAPI_Functions where possible
29207    //
29208    CMI5_STATE_BOOKMARK = "bookmark",
29209    CMI5_STATE_GENERIC_DATA = "generic_data",
29210    CMI5_STATE_TOTAL_TIME = "cumulative_time",
29211    CMI5_STATE_COMMITTED_STATUS = "status",
29212    CMI5_STATE_COMMENTS = "learner_comments",
29213    CMI5_STATE_SSP_BUCKET_PREFIX = "sspBucket",
29214    CMI5_STATE_LANGUAGE_PREFERENCE = "cmi.student_preference.language",
29215    CMI5_STATE_VOLUME_PREFERENCE = "cmi.student_preference.audio",
29216    CMI5_STATE_SPEED_PREFERENCE = "cmi.student_preference.speed",
29217    CMI5_STATE_TEXT_PREFERENCE = "cmi.student_preference.text",
29218
29219    // reserve the 2xx-5xx space for HTTP return codes from cmi5.js
29220    CMI5_NO_ERROR = "",
29221
29222    // these are non-HTTP status code errors from cmi5.js
29223    CMI5_CONN_ERROR = 998,
29224    CMI5_DEP_ERROR = 999,
29225
29226    CMI5_ERROR_NOT_IMPLEMENTED = 998,
29227    CMI5_ERROR_INVALID_SCORE = 1,
29228    CMI5_ERROR_UNRECOGNIZED_SSP_BUCKET = 2,
29229    CMI5_ERROR_UNRECOGNIZED_INTERACTION = 3,
29230    CMI5_ERROR_NON_NORMAL_MODE = 4,
29231    CMI5_ERROR_ALREADY_COMMITTED = 5,
29232    CMI5_ERROR_INVALID_STATUS_BASED_ON_SCORE = 6,
29233    CMI5_ERROR_SSP_BUCKET_LOAD_FAILED = 7,
29234    CMI5_ERROR_SSP_BUCKET_ALREADY_EXISTS = 8,
29235    CMI5_ERROR_SSP_BUCKET_INVALID_JSON = 9,
29236    CMI5_ERROR_INTERACTION_INVALID_JSON = 10,
29237    CMI5_ERROR_INTERACTION_LOAD_FAILED = 11,
29238
29239    intCMI5Error = CMI5_NO_ERROR,
29240    strCMI5ErrorString = "",
29241    strCMI5ErrorDiagnostic = "",
29242
29243    /* Declare global used for interacting via cmi5 */
29244    cmi5;
29245
29246function CMI5_SaveState (key, value, contentType, includeRegistration) {
29247    WriteToDebug("In CMI5_SaveState - key: " + key + ", value: " + value);
29248    var saveStateCfg = {
29249            agent: cmi5.getActor(),
29250            activity: cmi5.getActivity(),
29251            contentType: contentType
29252        },
29253        result,
29254        errCode,
29255        errMesg,
29256        errDiag;
29257
29258    if (typeof includeRegistration === "undefined" || includeRegistration === null || includeRegistration) {
29259        saveStateCfg.registration = cmi5.getRegistration();
29260    }
29261
29262    result = cmi5.getLRS().saveState(key, value, saveStateCfg);
29263    if (result.err !== null) {
29264        WriteToDebug("Failed to save " + key + " in state: " + result.err);
29265        errMesg = "Failed to save state: " + key;
29266
29267        //
29268        // in the case of errors result.err either contains an Error object (theoretically)
29269        // or the HTTP status code if the request actually made it to the LRS, to distinguish
29270        // we can check it for all digits (which is safer than checking for an Error object,
29271        // at least in this version of TinCanJS), which then allows us to look at the response
29272        // text which is generally a more helpful message than the status code
29273        //
29274        if (/^\d+$/.test(result.err)) {
29275            errCode = result.err;
29276
29277            if (result.err === 0) {
29278                errDiag = "Aborted, offline, or invalid CORS endpoint";
29279            }
29280            else {
29281                errDiag = result.xhr.responseText;
29282            }
29283        }
29284        else {
29285            errCode = CMI5_DEP_ERROR;
29286            errDiag = result.err;
29287        }
29288
29289        CMI5_SetErrorInfoManually(errCode, errMesg, errDiag);
29290        return false;
29291    }
29292
29293    return true;
29294}
29295
29296function CMI5_RetrieveState (key, includeRegistration) {
29297    WriteToDebug("In CMI5_RetrieveState - key: " + key);
29298    var retrieveStateCfg = {
29299            agent: cmi5.getActor(),
29300            activity: cmi5.getActivity()
29301        },
29302        result,
29303
29304        //
29305        // all of the state values ultimately have to be strings because
29306        // the SCORM values are strings, so even on an error we are supposed
29307        // to return an empty string
29308        //
29309        value = "",
29310        errCode,
29311        errMesg,
29312        errDiag;
29313
29314    if (typeof includeRegistration === "undefined" || includeRegistration === null || includeRegistration) {
29315        retrieveStateCfg.registration = cmi5.getRegistration();
29316    }
29317
29318    result = cmi5.getLRS().retrieveState(key, retrieveStateCfg);
29319    if (result.err !== null) {
29320        WriteToDebug("Failed to retrieve " + key + " from state: " + result.err);
29321        errMesg = "Failed to retrieve state: " + key;
29322
29323        //
29324        // in the case of errors result.err either contains an Error object (theoretically)
29325        // or the HTTP status code if the request actually made it to the LRS, to distinguish
29326        // we can check it for all digits (which is safer than checking for an Error object,
29327        // at least in this version of TinCanJS), which then allows us to look at the response
29328        // text which is generally a more helpful message than the status code
29329        //
29330        if (/^\d+$/.test(result.err)) {
29331            errCode = result.err;
29332
29333            if (result.err === 0) {
29334                errDiag = "Aborted, offline, or invalid CORS endpoint";
29335            }
29336            else {
29337                errDiag = result.xhr.responseText;
29338            }
29339        }
29340        else {
29341            errCode = CMI5_DEP_ERROR;
29342            errDiag = result.err;
29343        }
29344
29345        CMI5_SetErrorInfoManually(errCode, errMesg, errDiag);
29346        return "";
29347    }
29348
29349    if (result.state !== null) {
29350        value = result.state.contents;
29351    }
29352
29353    return value;
29354}
29355
29356//
29357// this is just here to make it possible to mock the launch URL
29358// for testing purposes
29359//
29360function CMI5_GetLaunchUrl () {
29361    WriteToDebug("In CMI5_GetLaunchUrl");
29362    return document.location.href;
29363}
29364
29365function CMI5_Initialize () {
29366    WriteToDebug("In CMI5_Initialize");
29367    var launchUrl;
29368
29369    Cmi5.prototype.log = function () {
29370        var mesg = "cmi5.js:",
29371            i;
29372
29373        for (i = 0; i < arguments.length; i += 1) {
29374            mesg += " ";
29375            mesg += typeof arguments[i] === "string" ? arguments[i] : JSON.stringify(arguments[i]);
29376        }
29377
29378        WriteToDebug(mesg);
29379    };
29380
29381    TinCan.LRS.prototype.log = function (msg, src) {
29382        src = src || this.LOG_SRC || "LRS";
29383        WriteToDebug("TinCan." + src + ": " + msg);
29384    };
29385
29386    try {
29387        launchUrl = CMI5_GetLaunchUrl();
29388
29389        WriteToDebug("CMI5_Initialize - instantiating cmi5 object from launch URL = " + launchUrl);
29390        cmi5 = new Cmi5(launchUrl);
29391        cmi5.includeSourceActivity(false);
29392    }
29393    catch (ex) {
29394        InitializeExecuted(false, "Failed to initialize cmi5 object (caught exception): " + ex);
29395        return;
29396    }
29397
29398    WriteToDebug("CMI5_Initialize - calling start on cmi5 instance");
29399    cmi5.start(
29400        function (err) {
29401            var committedState,
29402                result;
29403
29404            if (err !== null) {
29405                WriteToDebug("CMI5_Initialize - cmi5 start failed: " + err);
29406                InitializeExecuted(false, "Failed to initialize: " + err.message);
29407                return;
29408            }
29409            WriteToDebug("CMI5_Initialize - cmi5 start succeeded");
29410
29411            committedState = CMI5_RetrieveState(CMI5_STATE_COMMITTED_STATUS);
29412
29413            //
29414            // retrieve state doesn't provide a return value for detecting errors
29415            // so check to see if we have manually set errors (cause we shouldn't
29416            // unless the retrieve failed)
29417            //
29418            if (intCMI5Error) {
29419                WriteToDebug("CMI5_Initialize - failed to retrieve state for committed status: " + strCMI5ErrorString);
29420                InitializeExecuted(false, "Failed to initialize: " + strCMI5ErrorString);
29421                return;
29422            }
29423
29424            if (committedState !== "") {
29425                //
29426                // check committedState object for launchModes property because retrieveState
29427                // in TinCanJS tries to parse the JSON, and on failure eats the parse failure
29428                // and leaves the result contents as a string, but we always expect it to be
29429                // an object, and we know we shouldn't have saved bad JSON anyways
29430                //
29431                if (typeof committedState.launchModes === "undefined") {
29432                    WriteToDebug("CMI5_Initialize - failed to retrieve state for committed status: likely invalid JSON parse failure");
29433                    InitializeExecuted(false, "Failed to initialize: likely invalid JSON parse failure");
29434                    return;
29435                }
29436
29437                CMI5_COMMITTED_STATUS = committedState;
29438            }
29439
29440            if (CMI5_COMMITTED_STATUS.completion === true) {
29441                CMI5_ENTRY_MODE = ENTRY_REVIEW;
29442            }
29443            else if (CMI5_COMMITTED_STATUS.launchModes.length > 0) {
29444                CMI5_ENTRY_MODE = ENTRY_RESUME;
29445            }
29446            else {
29447                CMI5_ENTRY_MODE = ENTRY_FIRST_TIME;
29448            }
29449
29450            CMI5_COMMITTED_STATUS.launchModes.push(cmi5.getLaunchMode());
29451
29452            result = CMI5_SaveState(CMI5_STATE_COMMITTED_STATUS, CMI5_COMMITTED_STATUS, "application/json");
29453            if (! result) {
29454                WriteToDebug("CMI5_Initialize - failed to store committed status");
29455                InitializeExecuted(false, "Failed to initialize - failed to store committed status");
29456                return;
29457            }
29458
29459            WriteToDebug("CMI5_Initialize - succeeded");
29460            InitializeExecuted(true, "");
29461
29462            //
29463            // go ahead and try to cache this now, but don't have initialization
29464            // pending while trying to retrieve the state since it is only used
29465            // when Finish'ing which shouldn't happen immediately
29466            //
29467            window.setTimeout(
29468                function () {
29469                    CMI5_GetPreviouslyAccumulatedTime();
29470                },
29471                200
29472            );
29473        },
29474        {
29475            postFetch: function (err, response, parsed) {
29476                var rootActivity;
29477
29478                if (err !== null) {
29479                    return;
29480                }
29481
29482                //
29483                // now that we have a credential into the LRS see if the LRS has more info
29484                // on our Activity for statement capture, leaving this async, which could
29485                // result in a return *after* the initialized statement is recorded which
29486                // isn't really a problem, and experience indicates it usually doesn't happen
29487                //
29488                rootActivity = cmi5.getActivity();
29489                cmi5.getLRS().retrieveActivity(
29490                    rootActivity.id,
29491                    {
29492                        callback: function (err, result) {
29493                            if (err === null) {
29494                                cmi5.setActivity(result);
29495                            }
29496                        }
29497                    }
29498                );
29499            }
29500        }
29501    );
29502
29503    return true;
29504}
29505
29506function CMI5_CommitData () {
29507    WriteToDebug("In CMI5_CommitData");
29508    var i,
29509        st,
29510        auActivityId,
29511        errMesg,
29512        errCode,
29513        errDiag,
29514        saveCommittedStatus = false,
29515        sourceActivity = new TinCan.Activity(
29516            {
29517                id: "http://id.tincanapi.com/activity/software/scormdriver/" + VERSION,
29518                definition: {
29519                    name: {
29520                        und: "ScormDriver (" + VERSION + ")"
29521                    },
29522                    description: {
29523                        en: "ScormDriver (" + VERSION + ")"
29524                    },
29525                    type: "http://id.tincanapi.com/activitytype/source"
29526                }
29527            }
29528        );
29529
29530    CMI5_ClearErrorInfo();
29531
29532    //
29533    // commit the status updates first because the statement queue may have
29534    // a termination statement in it that ends the session preventing us from
29535    // making further calls
29536    //
29537    if (CMI5_PENDING_STATUS.completion !== null) {
29538        saveCommittedStatus = true;
29539
29540        CMI5_COMMITTED_STATUS.completion = CMI5_PENDING_STATUS.completion;
29541    }
29542    if (CMI5_PENDING_STATUS.success !== null) {
29543        saveCommittedStatus = true;
29544
29545        CMI5_COMMITTED_STATUS.success = CMI5_PENDING_STATUS.success;
29546        if (CMI5_PENDING_STATUS.score !== null) {
29547            CMI5_COMMITTED_STATUS.score = CMI5_PENDING_STATUS.score;
29548        }
29549    }
29550
29551    if (saveCommittedStatus) {
29552        result = CMI5_SaveState(CMI5_STATE_COMMITTED_STATUS, CMI5_COMMITTED_STATUS, "application/json");
29553        if (! result) {
29554            WriteToDebug("CMI5_CommitData - failed to commit status");
29555            return result;
29556        }
29557    }
29558
29559    if (CMI5_STATEMENT_QUEUE.length > 0) {
29560        if (CMI5_PENDING_STATUS.score !== null && CMI5_PENDING_STATUS.success !== null) {
29561            //
29562            // need to loop statements looking for passed/failed with this AU id
29563            // and set the score into the statement - this is what allows us to
29564            // allow the user to call SetScore multiple times
29565            //
29566            auActivityId = cmi5.getActivity().id;
29567
29568            for (i = 0; i < CMI5_STATEMENT_QUEUE.length; i += 1) {
29569                st = CMI5_STATEMENT_QUEUE[i];
29570
29571                if (st.target.id === auActivityId && (st.verb.id === CMI5_VERB_ID_FAILED || st.verb.id === CMI5_VERB_ID_PASSED)) {
29572                    st.result = st.result || new TinCan.Result();
29573                    st.result.score = new TinCan.Score(CMI5_PENDING_STATUS.score);
29574                }
29575            }
29576        }
29577
29578        for (i = 0; i < CMI5_STATEMENT_QUEUE.length; i += 1) {
29579            st = CMI5_STATEMENT_QUEUE[i];
29580
29581            st.context.contextActivities.grouping = st.context.contextActivities.grouping || [];
29582            st.context.contextActivities.grouping.push(sourceActivity);
29583        }
29584
29585        result = cmi5.getLRS().saveStatements(CMI5_STATEMENT_QUEUE);
29586        if (result.err !== null) {
29587            errMesg = "Failed to commit data: statements";
29588
29589            //
29590            // in the case of errors result.err either contains an Error object (theoretically)
29591            // or the HTTP status code if the request actually made it to the LRS, to distinguish
29592            // we can check it for all digits (which is safer than checking for an Error object,
29593            // at least in this version of TinCanJS), which then allows us to look at the response
29594            // text which is generally a more helpful message than the status code
29595            //
29596            if (/^\d+$/.test(result.err)) {
29597                if (result.err === 0) {
29598                    // 0 status codes represent network errors in the request but 0 can't be used
29599                    // as the error code because Driver treats 0 as the no error code
29600                    errCode = CMI5_CONN_ERROR;
29601                    errDiag = "Aborted, offline, or invalid CORS endpoint";
29602                }
29603                else {
29604                    errCode = result.err;
29605                    errDiag = result.xhr.responseText;
29606                }
29607            }
29608            else {
29609                errCode = CMI5_DEP_ERROR;
29610                errDiag = result.err;
29611            }
29612
29613            CMI5_SetErrorInfoManually(errCode, errMesg, errDiag);
29614            return false;
29615        }
29616
29617        CMI5_STATEMENT_QUEUE = [];
29618    }
29619
29620    return true;
29621}
29622
29623function CMI5_Finish (exitType, statusWasSet) {
29624    WriteToDebug("In CMI5_Finish - exitType: " + exitType);
29625    var st = cmi5.terminatedStatement(),
29626        result;
29627
29628    CMI5_ClearErrorInfo();
29629
29630    CMI5_SetDurationInStatement(st);
29631
29632    // FEATURE: record something related to the exit type, for instance on timeout set extension?
29633
29634    CMI5_STATEMENT_QUEUE.push(st);
29635
29636    result = CMI5_SaveState(CMI5_STATE_TOTAL_TIME, CMI5_GetPreviouslyAccumulatedTime() + GetSessionAccumulatedTime(), "text/plain");
29637    if (! result) {
29638        WriteToDebug("CMI5_Finish - failed to record total time to State");
29639        return false;
29640    }
29641
29642    result = CMI5_CommitData();
29643    if (! result) {
29644        WriteToDebug("CMI5_Finish - failed to commit data");
29645        return false;
29646    }
29647
29648    return true;
29649}
29650
29651function CMI5_ConcedeControl () {
29652    WriteToDebug("In CMI5_ConcedeControl");
29653    var returnURL = cmi5.getReturnURL(),
29654        contentRoot = SearchParentsForContentRoot();
29655
29656    //
29657    // if the LMS provided a return URL then the AU must honor it
29658    //
29659    if (returnURL !== null && contentRoot !== null) {
29660        contentRoot.location.assign(returnURL);
29661        return true;
29662    }
29663
29664    //
29665    // no return URL was provided so try to be a good citizen and
29666    // close ourselves, if this fails to close then we weren't
29667    // opened as part of a script call so there isn't much else
29668    // we can be expected to do
29669    //
29670    if (contentRoot !== null) {
29671        contentRoot.close();
29672        return true;
29673    }
29674
29675    return true;
29676}
29677
29678function CMI5_GetStudentID () {
29679    WriteToDebug("In CMI5_GetStudentID");
29680    var actor = cmi5.getActor();
29681
29682    //
29683    // it was discussed about whether returning just the account name
29684    // was a proper approach and decided because SD is really only
29685    // supported by LMS launch that the account would normally only
29686    // be used with the launching system's account and in that
29687    // case the homePage isn't strictly necessary for ID determination
29688    //
29689    // more advanced use cases should avoid using this method if they
29690    // require the homePage as well
29691    //
29692    return actor.account.name;
29693}
29694
29695function CMI5_GetStudentName () {
29696    WriteToDebug("In CMI5_GetStudentName");
29697
29698    return cmi5.getActor().toString();
29699}
29700
29701function CMI5_GetBookmark () {
29702    WriteToDebug("In CMI5_GetBookmark");
29703
29704    return CMI5_RetrieveState(CMI5_STATE_BOOKMARK);
29705}
29706
29707//
29708// description is optional and not part of the original interface, so GetBookmark need only
29709// support the 'location' argument, so no need to store description which was added to support
29710// capturing xAPI data for it
29711//
29712function CMI5_SetBookmark (location, description) {
29713    WriteToDebug("In CMI5_SetBookmark - location: " + location + ", description: " + description);
29714    var st,
29715        targetId = CreateValidIdentifier(location);
29716
29717    //
29718    // Driver's interface has always (well, long enough anyways) returned
29719    // empty string for "valid identifier" when passed `undefined`, `null`
29720    // or "" which is clearly not a valid identifier, so prevent writing
29721    // the statement in that case
29722    //
29723    if (targetId !== "") {
29724        // FEATURE: allow them to disable capture of these statements via launchParameters
29725        st = cmi5.prepareStatement("http://adlnet.gov/expapi/verbs/experienced");
29726
29727        //
29728        // preparing a cmi5 statement sets the target as the AU's Activity
29729        // but we want to make that the parent and instead use the bookmarked Activity
29730        // as the target of the allowed statement
29731        //
29732        st.context.contextActivities = st.context.contextActivities || new TinCan.ContextActivities();
29733        st.context.contextActivities.parent = st.context.contextActivities.parent || [];
29734        st.context.contextActivities.parent.push(st.target);
29735
29736        //
29737        // now with the parent set to the AU Activity set up our bookmarked Activity
29738        // as the target of the statement
29739        //
29740        st.target = new TinCan.Activity(
29741            {
29742                id: targetId
29743            }
29744        );
29745
29746        CMI5_SetDurationInStatement(st);
29747
29748        CMI5_STATEMENT_QUEUE.push(st);
29749    }
29750    else {
29751        WriteToDebug("WARNING - No bookmarking statement written, empty location");
29752    }
29753
29754    return CMI5_SaveState(CMI5_STATE_BOOKMARK, location, "text/plain");
29755}
29756
29757function CMI5_GetDataChunk () {
29758    WriteToDebug("In CMI5_GetDataChunk");
29759
29760    return CMI5_RetrieveState(CMI5_STATE_GENERIC_DATA);
29761}
29762
29763function CMI5_SetDataChunk (value) {
29764    WriteToDebug("In CMI5_SetDataChunk");
29765
29766    return CMI5_SaveState(CMI5_STATE_GENERIC_DATA, value, "text/plain");
29767}
29768
29769function CMI5_GetLaunchData () {
29770    WriteToDebug("In CMI5_GetLaunchData");
29771    var result = cmi5.getLaunchParameters();
29772
29773    if (result === null) {
29774        result = "";
29775    }
29776
29777    return result;
29778}
29779
29780/*****************************
29781* Preferences
29782*****************************/
29783
29784function CMI5_GetAudioPlayPreference () {
29785    WriteToDebug("In CMI5_GetAudioPlayPreference");
29786    var audioPref;
29787
29788    CMI5_ClearErrorInfo();
29789
29790    if (CMI5_PREF_AUDIO_PLAY === null) {
29791        audioPref = cmi5.getAudioPreference();
29792
29793        if (audioPref === "on") {
29794            WriteToDebug("    Caching On");
29795            CMI5_PREF_AUDIO_PLAY = PREFERENCE_ON;
29796        }
29797        else if (audioPref === "off") {
29798            WriteToDebug("    Caching Off");
29799            CMI5_PREF_AUDIO_PLAY = PREFERENCE_OFF;
29800        }
29801        else {
29802            WriteToDebug("    Caching Default");
29803            CMI5_PREF_AUDIO_PLAY = PREFERENCE_DEFAULT;
29804        }
29805    }
29806
29807    WriteToDebug("Returning preference: " + CMI5_PREF_AUDIO_PLAY);
29808    return CMI5_PREF_AUDIO_PLAY;
29809}
29810
29811function CMI5_GetAudioVolumePreference () {
29812    WriteToDebug("In CMI5_GetAudioVolumePreference");
29813    var volumePref;
29814
29815    CMI5_ClearErrorInfo();
29816
29817    if (CMI5_PREF_AUDIO_VOLUME === null) {
29818        volumePref = CMI5_RetrieveState(CMI5_STATE_VOLUME_PREFERENCE, false);
29819        if (volumePref === "") {
29820            volumePref = 100;
29821        }
29822
29823        CMI5_PREF_AUDIO_VOLUME = parseInt(volumePref, 10);
29824    }
29825
29826    WriteToDebug("Returning " + CMI5_PREF_AUDIO_VOLUME);
29827    return CMI5_PREF_AUDIO_VOLUME;
29828}
29829
29830function CMI5_SetAudioPreference (PlayPreference, intPercentOfMaxVolume) {
29831    WriteToDebug("In CMI5_SetAudioPreference PlayPreference=" + PlayPreference + ", intPercentOfMaxVolume=" + intPercentOfMaxVolume);
29832    var currentPlayPreference = cmi5.getAudioPreference(),
29833        overallResult = true,
29834        prefChanged = false,
29835        result;
29836
29837    CMI5_ClearErrorInfo();
29838
29839    // update the local caches
29840    CMI5_PREF_AUDIO_PLAY = PlayPreference;
29841    CMI5_PREF_AUDIO_VOLUME = intPercentOfMaxVolume;
29842
29843    if ((PlayPreference == PREFERENCE_OFF) && (currentPlayPreference === null || currentPlayPreference === "on")) {
29844        // set preference to off
29845        cmi5.setAudioPreference("off");
29846        prefChanged = true;
29847    }
29848    else if ((PlayPreference != PREFERENCE_OFF) && (currentPlayPreference === null || currentPlayPreference === "off")) {
29849        // set preference to on
29850        cmi5.setAudioPreference("on");
29851        prefChanged = true;
29852    }
29853
29854    if (prefChanged) {
29855        result = cmi5.saveLearnerPrefs();
29856        if (result) {
29857            WriteToDebug("  failed to save learner prefs, setting overall result to false");
29858            overallResult = false;
29859
29860            CMI5_SetErrorInfoManually(CMI5_DEP_ERROR, "Failed to save learner preferences", result);
29861        }
29862    }
29863
29864    result = CMI5_SaveState(CMI5_STATE_VOLUME_PREFERENCE, intPercentOfMaxVolume, "text/plain", false);
29865    if (! result) {
29866        WriteToDebug("  failed to save volume preference in state, setting overall result to false");
29867        overallResult = false;
29868    }
29869
29870    return overallResult;
29871}
29872
29873//
29874// implemented the language preference using state instead of cmi5's built in language
29875// preference because Driver's interface allows for any arbitrary string, but cmi5 doesn't;
29876// don't save with this registration because a user might want it across multiple registrations
29877//
29878function CMI5_SetLanguagePreference (strLanguage) {
29879    WriteToDebug("In CMI5_SetLanguagePreference strLanguage=" + strLanguage);
29880
29881    CMI5_ClearErrorInfo();
29882
29883    CMI5_PREF_LANGUAGE = strLanguage;
29884
29885    return CMI5_SaveState(CMI5_STATE_LANGUAGE_PREFERENCE, strLanguage, "text/plain", false);
29886}
29887
29888function CMI5_GetLanguagePreference () {
29889    WriteToDebug("In CMI5_GetLanguagePreference");
29890
29891    CMI5_ClearErrorInfo();
29892
29893    if (CMI5_PREF_LANGUAGE === null) {
29894        CMI5_PREF_LANGUAGE = CMI5_RetrieveState(CMI5_STATE_LANGUAGE_PREFERENCE, false);
29895    }
29896
29897    return CMI5_PREF_LANGUAGE;
29898}
29899
29900function CMI5_SetSpeedPreference (intPercentOfMax) {
29901    WriteToDebug("In CMI5_SetSpeedPreference intPercentOfMax=" + intPercentOfMax);
29902
29903    CMI5_ClearErrorInfo();
29904
29905    CMI5_PREF_SPEED = intPercentOfMax;
29906
29907    return CMI5_SaveState(CMI5_STATE_SPEED_PREFERENCE, intPercentOfMax, "text/plain", false);
29908}
29909
29910function CMI5_GetSpeedPreference () {
29911    WriteToDebug("In CMI5_GetSpeedPreference");
29912    var pref;
29913
29914    CMI5_ClearErrorInfo();
29915
29916    if (CMI5_PREF_SPEED === null) {
29917        pref = CMI5_RetrieveState(CMI5_STATE_SPEED_PREFERENCE, false);
29918        if (pref === "") {
29919            pref = 100;
29920        }
29921
29922        CMI5_PREF_SPEED = pref;
29923    }
29924
29925    return CMI5_PREF_SPEED;
29926}
29927
29928function CMI5_SetTextPreference (intPreference) {
29929    WriteToDebug("In CMI5_SetTextPreference intPreference=" + intPreference);
29930
29931    CMI5_ClearErrorInfo();
29932
29933    CMI5_PREF_TEXT = intPreference;
29934
29935    return CMI5_SaveState(CMI5_STATE_TEXT_PREFERENCE, intPreference, "text/plain", false);
29936}
29937
29938function CMI5_GetTextPreference () {
29939    WriteToDebug("In CMI5_GetTextPreference");
29940
29941    CMI5_ClearErrorInfo();
29942
29943    if (CMI5_PREF_TEXT === null) {
29944        CMI5_PREF_TEXT = CMI5_RetrieveState(CMI5_STATE_TEXT_PREFERENCE, false);
29945        if (CMI5_PREF_TEXT === "") {
29946            CMI5_PREF_TEXT = PREFERENCE_DEFAULT;
29947        }
29948    }
29949
29950    return CMI5_PREF_TEXT;
29951}
29952
29953function CMI5_GetEntryMode () {
29954    WriteToDebug("In CMI5_GetEntryMode");
29955
29956    CMI5_ClearErrorInfo();
29957
29958    // this should be set during initialize
29959    return CMI5_ENTRY_MODE;
29960}
29961
29962function CMI5_GetLessonMode () {
29963    WriteToDebug("In CMI5_GetLessonMode - launchMode: " + cmi5.getLaunchMode());
29964    var launchMode = cmi5.getLaunchMode();
29965
29966    CMI5_ClearErrorInfo();
29967
29968    if (launchMode === "Normal") {
29969        return MODE_NORMAL;
29970    }
29971    else if (launchMode === "Browse") {
29972        return MODE_BROWSE;
29973    }
29974    else if (launchMode === "Review") {
29975        return MODE_REVIEW;
29976    }
29977
29978    CMI5_SetErrorInfoManually(CMI5_DEP_ERROR, "Invalid launch mode from LMS", "launchMode = " + launchMode);
29979
29980    return null;
29981}
29982
29983function CMI5_GetTakingForCredit () {
29984    WriteToDebug("In CMI5_GetTakingForCredit - launchMode: " + cmi5.getLaunchMode());
29985
29986    if (cmi5.getLaunchMode() === "Normal") {
29987        return true;
29988    }
29989    return false;
29990}
29991
29992/*****************************
29993*  Time management
29994*****************************/
29995
29996//
29997// returns number of milliseconds duration of all launches previous
29998// to this session
29999//
30000function CMI5_GetPreviouslyAccumulatedTime () {
30001    var retrievedValue;
30002
30003    WriteToDebug("In CMI5_GetPreviouslyAccumulatedTime - cached: " + CMI5_TOTAL_PREV_DURATION);
30004    if (CMI5_TOTAL_PREV_DURATION === null) {
30005        retrievedValue = CMI5_RetrieveState(CMI5_STATE_TOTAL_TIME);
30006        WriteToDebug("   fetched previous duration for caching: " + retrievedValue);
30007
30008        if (retrievedValue === "") {
30009            WriteToDebug("   empty value converting to zero");
30010            CMI5_TOTAL_PREV_DURATION = 0;
30011        }
30012        else {
30013            CMI5_TOTAL_PREV_DURATION = parseInt(retrievedValue, 10);
30014        }
30015    }
30016
30017    return CMI5_TOTAL_PREV_DURATION;
30018}
30019
30020function CMI5_SaveTime (intMilliSeconds) {
30021    WriteToDebug("In CMI5_SaveTime");
30022
30023    CMI5_SESSION_DURATION = intMilliSeconds;
30024
30025    return true;
30026}
30027
30028function CMI5_GetMaxTimeAllowed () {
30029    WriteToDebug("In CMI5_GetMaxTimeAllowed");
30030    return MAX_CMI_TIME;
30031}
30032
30033/*****************************
30034*  Scoring
30035*****************************/
30036
30037function CMI5_SetScore (intScore, intMaxScore, intMinScore) {
30038    WriteToDebug("In CMI5_SetScore intScore=" + intScore + ", intMaxScore=" + intMaxScore + ", intMinScore=" + intMinScore);
30039    var score,
30040        masteryScore;
30041
30042    CMI5_ClearErrorInfo();
30043
30044    if (cmi5.getLaunchMode() !== "Normal") {
30045        CMI5_SetErrorInfoManually(CMI5_ERROR_NON_NORMAL_MODE, "Failed to SetScore: " + intScore, "launch mode (" + cmi5.getLaunchMode() + ") 
30045not a recording mode");
30046        return false;
30047    }
30048
30049    //
30050    // check if passed has already been committed, if so can't set a score to get recorded
30051    //
30052    if (CMI5_COMMITTED_STATUS.success === true) {
30053        CMI5_SetErrorInfoManually(CMI5_ERROR_ALREADY_COMMITTED, "Failed to SetScore: " + intScore, "success status already committed");
30054        return false;
30055    }
30056
30057    score = {
30058        raw: Math.round(intScore),
30059        max: intMaxScore,
30060        min: intMinScore,
30061
30062        //
30063        // SCORM 1.2 used integer values between 0 and 100 for score, 2004 introduced scaled value
30064        // so Driver's API is based on the raw value that gets normalized for the scaled value, so
30065        // we divide by 100 and then round to 2 decimal places because theoretically an integer
30066        // couldn't have more precision
30067        //
30068        // of course this is very silly in the case when min isn't 0, and/or max isn't 100 because
30069        // then the scaled score is completely out of whack, for example:
30070        //
30071        // min 0, max 4, score 2 === 0.02
30072        // min 0, max 4, masteryScore = 0.5, so passing score === 50
30073        //
30074        // *but* we can't really change Driver's implementation at this point as those ships have
30075        // sailed, so just perpetuate the awfulness
30076        //
30077        scaled: RoundToPrecision(intScore / 100, 2)
30078    };
30079
30080    try {
30081        cmi5.validateScore(score);
30082    }
30083    catch (ex) {
30084        CMI5_SetErrorInfoManually(CMI5_ERROR_INVALID_SCORE, "Failed to SetScore: " + intScore, "invalid score: " + ex.message);
30085        return false;
30086    }
30087
30088    CMI5_PENDING_STATUS.score = score;
30089
30090    //
30091    // to mimic the SCORM behavior, check to see if there is a masteryScore and if so
30092    // determine pass/fail and set that into the pending state
30093    //
30094    masteryScore = cmi5.getMasteryScore();
30095    if (masteryScore !== null) {
30096        if (score.scaled >= masteryScore) {
30097            WriteToDebug("   automatically calling SetPassed based on masteryScore");
30098            return CMI5_SetPassed();
30099        }
30100        else if (score.scaled < masteryScore) {
30101            WriteToDebug("   automatically calling SetFailed based on masteryScore");
30102            return CMI5_SetFailed();
30103        }
30104    }
30105
30106    return true;
30107}
30108
30109function CMI5_GetScore () {
30110    WriteToDebug("In CMI5_GetScore");
30111    var result = "";
30112
30113    CMI5_ClearErrorInfo();
30114
30115    if (CMI5_COMMITTED_STATUS !== null && typeof CMI5_COMMITTED_STATUS.score !== "undefined" && CMI5_COMMITTED_STATUS.score !== null) {
30116        result = CMI5_COMMITTED_STATUS.score.raw;
30117    }
30118    else if (CMI5_PENDING_STATUS.score !== null) {
30119        result = CMI5_PENDING_STATUS.score.raw;
30120    }
30121
30122    return result;
30123}
30124
30125function CMI5_GetScaledScore () {
30126    WriteToDebug("CMI5_GetScaledScore");
30127    var result = "";
30128
30129    CMI5_ClearErrorInfo();
30130
30131    if (CMI5_COMMITTED_STATUS !== null && typeof CMI5_COMMITTED_STATUS.score !== "undefined" && CMI5_COMMITTED_STATUS.score !== null) {
30132        result = CMI5_COMMITTED_STATUS.score.scaled;
30133    }
30134    else if (CMI5_PENDING_STATUS.score !== null) {
30135        result = CMI5_PENDING_STATUS.score.scaled;
30136    }
30137
30138    return result;
30139}
30140
30141function CMI5_SetPointBasedScore (intScore, intMaxScore, intMinScore) {
30142    WriteToDebug("CMI5_SetPointBasedScore - cmi5 does not support SetPointBasedScore, falling back to SetScore");
30143    return CMI5_SetScore(intScore, intMaxScore, intMinScore);
30144}
30145
30146/***********************
30147*    Interactions
30148************************/
30149
30150//
30151// strID and strLearningObjectiveID come in from CreateValidIdentifier so already
30152// have the AU id attached when needed
30153//
30154// strAlternateResponse and strAlternateCorrectResponse are for LMSs that can't
30155// handle longer values, we never need them
30156//
30157// dtmTime is just the current date/time which we capture as the 'timestamp' in
30158// the statements anyways so no need to use it (cause the API user can't pass it)
30159//
30160function CMI5_RecordInteraction (
30161    strID,
30162    strResponse,
30163    blnCorrect,
30164    strCorrectResponse,
30165    strDescription,
30166    intWeighting,
30167    intLatency,
30168    strLearningObjectiveID,
30169    dtmTime,
30170    interactionType,
30171    strAlternateResponse,
30172    strAlternateCorrectResponse
30173) {
30174    var st;
30175
30176    CMI5_ClearErrorInfo();
30177
30178    if (cmi5.getLaunchMode() !== "Normal") {
30179        CMI5_SetErrorInfoManually(CMI5_ERROR_NON_NORMAL_MODE, "Failed to RecordInteraction: launch mode (" + cmi5.getLaunchMode() + ") 
30179not a recording mode");
30180        return false;
30181    }
30182
30183    st = cmi5.prepareStatement("http://adlnet.gov/expapi/verbs/answered");
30184
30185    st.result = st.result || new TinCan.Result();
30186    if (strResponse !== null) {
30187        st.result.response = strResponse;
30188    }
30189
30190    //
30191    // for INTERACTION_RESULT_UNANTICIPATED or INTERACTION_RESULT_NEUTRAL just
30192    // leave .success out
30193    //
30194    if (blnCorrect === true || blnCorrect === INTERACTION_RESULT_CORRECT) {
30195        st.result.success = true;
30196    }
30197    else if (blnCorrect === false || blnCorrect === "" || blnCorrect === "false" || blnCorrect === INTERACTION_RESULT_WRONG) {
30198        st.result.success = false;
30199    }
30200
30201    if (intLatency !== null && intLatency !== "") {
30202        st.result.duration = TinCan.Utils.convertMillisecondsToISO8601Duration(intLatency);
30203    }
30204    if (intWeighting !== null && intWeighting !== "") {
30205        st.result.extensions = st.result.extensions || {};
30206        st.result.extensions["http://id.tincanapi.com/extension/cmi-interaction-weighting"] = intWeighting;
30207    }
30208
30209    //
30210    // preparing a cmi5 statement sets the target as the AU's Activity
30211    // but we want to make that the parent and instead use the interaction Activity
30212    // as the target of the allowed statement
30213    //
30214    st.context.contextActivities = st.context.contextActivities || new TinCan.ContextActivities();
30215    st.context.contextActivities.parent = st.context.contextActivities.parent || [];
30216    st.context.contextActivities.parent.push(st.target);
30217
30218    if (strLearningObjectiveID !== "") {
30219        st.context.contextActivities.grouping = st.context.contextActivities.grouping || [];
30220        st.context.contextActivities.grouping.push(
30221            new TinCan.Activity(
30222                {
30223                    id: strLearningObjectiveID
30224                }
30225            )
30226        );
30227    }
30228
30229    //
30230    // now with the parent set to the AU Activity set up our interaction Activity
30231    // as the target of the statement
30232    //
30233    st.target = new TinCan.Activity(
30234        {
30235            id: strID,
30236            definition: {
30237                type: CMI5_INTERACTION_ACTIVITY_TYPE,
30238                interactionType: interactionType
30239            }
30240        }
30241    );
30242
30243    // FEATURE: provide a way to configure what language is in use?
30244    if (strDescription !== null && strDescription !== "") {
30245        st.target.definition.description = {
30246            und: strDescription
30247        };
30248    }
30249
30250    //
30251    // this makes it so that "" can never be a correct response, but that's the way
30252    // the other standards in Driver work, and is potentially a reasonable expectation
30253    //
30254    if (strCorrectResponse !== null && strCorrectResponse !== "") {
30255        st.target.definition.correctResponsesPattern = [strCorrectResponse];
30256    }
30257
30258    CMI5_STATEMENT_QUEUE.push(st);
30259
30260    CMI5_CreateInteraction.apply(this, arguments);
30261    CMI5_SaveInteraction(strID);
30262
30263    return true;
30264}
30265
30266function CMI5_RecordTrueFalseInteraction (strID, blnResponse, blnCorrect, blnCorrectResponse, strDescription, intWeighting, intLatency, strLearningObjectiveID, dtmTime) {
30267    WriteToDebug(
30268        "In CMI5_RecordTrueFalseInteraction strID=" + strID + ", strResponse=" + strResponse +
30269        ", blnCorrect=" + blnCorrect + ", strCorrectResponse=" + strCorrectResponse +
30270        ", strDescription=" + strDescription + ", intWeighting=" + intWeighting + ", intLatency=" + intLatency +
30271        ", strLearningObjectiveID=" + strLearningObjectiveID + ", dtmTime=" + dtmTime
30272    );
30273
30274    var strResponse = null,
30275        strCorrectResponse = null;
30276
30277    if (blnResponse === true) {
30278        strResponse = "true";
30279    }
30280    // test for false b/c it could be null in which case we want to leave it as `null`
30281    else if (blnResponse === false) {
30282        strResponse = "false";
30283    }
30284
30285    if (blnCorrectResponse === true) {
30286        strCorrectResponse = "true";
30287    }
30288    // test for false b/c it could be null in which case we want to leave it as `null`
30289    else if (blnCorrectResponse === false) {
30290        strCorrectResponse = "false";
30291    }
30292
30293    return CMI5_RecordInteraction(
30294        strID,
30295        strResponse,
30296        blnCorrect,
30297        strCorrectResponse,
30298        strDescription,
30299        intWeighting,
30300        intLatency,
30301        strLearningObjectiveID,
30302        dtmTime,
30303        CMI5_INTERACTION_TYPE_TRUE_FALSE
30304    );
30305}
30306
30307function CMI5_RecordMultipleChoiceInteraction (strID, aryResponse, blnCorrect, aryCorrectResponse, strDescription, intWeighting, intLatency, strLearningObjectiveID, dtmTime) {
30308    WriteToDebug(
30309        "In CMI5_RecordMultipleChoiceInteraction strID=" + strID + ", aryResponse=" + aryResponse +
30310        ", blnCorrect=" + blnCorrect + ", aryCorrectResponse=" + aryCorrectResponse +
30311        ", strDescription=" + strDescription + ", intWeighting=" + intWeighting + ", intLatency=" + intLatency +
30312        ", strLearningObjectiveID=" + strLearningObjectiveID + ", dtmTime=" + dtmTime
30313    );
30314
30315    var strResponse = null,
30316        strCorrectResponse = "",
30317        i;
30318
30319    if (aryResponse !== null) {
30320        strResponse = "";
30321
30322        for (i = 0; i < aryResponse.length; i += 1) {
30323            if (strResponse.length > 0) {
30324                strResponse += "[,]";
30325            }
30326
30327            strResponse += (aryResponse[i].Long !== "" ? aryResponse[i].Long : aryResponse[i].Short);
30328        }
30329    }
30330
30331    for (i = 0; i < aryCorrectResponse.length; i += 1) {
30332        if (strCorrectResponse.length > 0) {
30333            strCorrectResponse += "[,]";
30334        }
30335
30336        strCorrectResponse += (aryCorrectResponse[i].Long !== "" ? aryCorrectResponse[i].Long : aryCorrectResponse[i].Short);
30337    }
30338
30339    return CMI5_RecordInteraction(
30340        strID,
30341        strResponse,
30342        blnCorrect,
30343        strCorrectResponse,
30344        strDescription,
30345        intWeighting,
30346        intLatency,
30347        strLearningObjectiveID,
30348        dtmTime,
30349        CMI5_INTERACTION_TYPE_CHOICE
30350    );
30351}
30352
30353function CMI5_RecordFillInInteraction (strID, strResponse, blnCorrect, strCorrectResponse, strDescription, intWeighting, intLatency, strLearningObjectiveID, dtmTime) {
30354    WriteToDebug(
30355        "In CMI5_RecordFillInInteraction strID=" + strID + ", strResponse=" + strResponse +
30356        ", blnCorrect=" + blnCorrect + ", strCorrectResponse=" + strCorrectResponse +
30357        ", strDescription=" + strDescription + ", intWeighting=" + intWeighting + ", intLatency=" + intLatency +
30358        ", strLearningObjectiveID=" + strLearningObjectiveID + ", dtmTime=" + dtmTime
30359    );
30360
30361    var interactionType = CMI5_INTERACTION_TYPE_FILL_IN;
30362
30363    if (strResponse !== null) {
30364        strResponse = new String(strResponse);
30365    }
30366
30367    if (strCorrectResponse !== null) {
30368        strCorrectResponse = new String(strCorrectResponse);
30369
30370        if (strCorrectResponse.length > 250) {
30371            interactionType = CMI5_INTERACTION_TYPE_LONG_FILL_IN;
30372        }
30373    }
30374
30375    return CMI5_RecordInteraction(
30376        strID,
30377        strResponse,
30378        blnCorrect,
30379        strCorrectResponse,
30380        strDescription,
30381        intWeighting,
30382        intLatency,
30383        strLearningObjectiveID,
30384        dtmTime,
30385        interactionType
30386    );
30387}
30388
30389function CMI5_RecordMatchingInteraction (strID, aryResponse, blnCorrect, aryCorrectResponse, strDescription, intWeighting, intLatency, strLearningObjectiveID, dtmTime) {
30390    WriteToDebug(
30391        "In CMI5_RecordMatchingInteraction strID=" + strID + ", aryResponse=" + aryResponse +
30392        ", blnCorrect=" + blnCorrect + ", aryCorrectResponse=" + aryCorrectResponse +
30393        ", strDescription=" + strDescription + ", intWeighting=" + intWeighting + ", intLatency=" + intLatency +
30394        ", strLearningObjectiveID=" + strLearningObjectiveID + ", dtmTime=" + dtmTime
30395    );
30396
30397    var strResponse = null,
30398        strCorrectResponse = "",
30399        i;
30400
30401    if (aryResponse !== null) {
30402        strResponse = "";
30403
30404        for (i = 0; i < aryResponse.length; i += 1) {
30405            if (strResponse.length > 0) {
30406                strResponse += "[,]";
30407            }
30408
30409            strResponse += (aryResponse[i].Source.Long !== "" ? aryResponse[i].Source.Long : aryResponse[i].Source.Short) + "[.]" + (aryResponse[i].Target.Long !== "" ? aryResponse[i].Target.Long : aryResponse[i].Target.Short);
30410        }
30411    }
30412
30413    for (i = 0; i < aryCorrectResponse.length; i += 1) {
30414        if (strCorrectResponse.length > 0) {
30415            strCorrectResponse += "[,]";
30416        }
30417
30418        strCorrectResponse += (aryCorrectResponse[i].Source.Long !== "" ? aryCorrectResponse[i].Source.Long : aryCorrectResponse[i].Source.Short) + "[.]" + (aryCorrectResponse[i].Target.Long !== "" ? aryCorrectResponse[i].Target.Long : aryCorrectResponse[i].Target.Short);
30419    }
30420
30421    return CMI5_RecordInteraction(
30422        strID,
30423        strResponse,
30424        blnCorrect,
30425        strCorrectResponse,
30426        strDescription,
30427        intWeighting,
30428        intLatency,
30429        strLearningObjectiveID,
30430        dtmTime,
30431        CMI5_INTERACTION_TYPE_MATCHING
30432    );
30433}
30434
30435function CMI5_RecordPerformanceInteraction (strID, strResponse, blnCorrect, strCorrectResponse, strDescription, intWeighting, intLatency, strLearningObjectiveID, dtmTime) {
30436    WriteToDebug(
30437        "In CMI5_RecordPerformanceInteraction strID=" + strID + ", strResponse=" + strResponse +
30438        ", blnCorrect=" + blnCorrect + ", strCorrectResponse=" + strCorrectResponse +
30439        ", strDescription=" + strDescription + ", intWeighting=" + intWeighting + ", intLatency=" + intLatency +
30440        ", strLearningObjectiveID=" + strLearningObjectiveID + ", dtmTime=" + dtmTime
30441    );
30442
30443    if (strResponse !== null) {
30444        strResponse = new String(strResponse);
30445    }
30446
30447    if (strCorrectResponse !== null) {
30448        strCorrectResponse = new String(strCorrectResponse);
30449    }
30450
30451    return CMI5_RecordInteraction(
30452        strID,
30453        strResponse,
30454        blnCorrect,
30455        strCorrectResponse,
30456        strDescription,
30457        intWeighting,
30458        intLatency,
30459        strLearningObjectiveID,
30460        dtmTime,
30461        CMI5_INTERACTION_TYPE_PERFORMANCE
30462    );
30463}
30464
30465function CMI5_RecordSequencingInteraction (strID, aryResponse, blnCorrect, aryCorrectResponse, strDescription, intWeighting, intLatency, strLearningObjectiveID, dtmTime) {
30466    WriteToDebug(
30467        "In CMI5_RecordSequencingInteraction strID=" + strID + ", aryResponse=" + aryResponse +
30468        ", blnCorrect=" + blnCorrect + ", aryCorrectResponse=" + aryCorrectResponse +
30469        ", strDescription=" + strDescription + ", intWeighting=" + intWeighting + ", intLatency=" + intLatency +
30470        ", strLearningObjectiveID=" + strLearningObjectiveID + ", dtmTime=" + dtmTime
30471    );
30472
30473    var strResponse = null,
30474        strCorrectResponse = "",
30475        i;
30476
30477    if (aryResponse !== null) {
30478        strResponse = "";
30479
30480        for (i = 0; i < aryResponse.length; i += 1) {
30481            if (strResponse.length > 0) {
30482                strResponse += "[,]";
30483            }
30484
30485            strResponse += (aryResponse[i].Long !== "" ? aryResponse[i].Long : aryResponse[i].Short);
30486        }
30487    }
30488
30489    for (i = 0; i < aryCorrectResponse.length; i += 1) {
30490        if (strCorrectResponse.length > 0) {
30491            strCorrectResponse += "[,]";
30492        }
30493
30494        strCorrectResponse += (aryCorrectResponse[i].Long !== "" ? aryCorrectResponse[i].Long : aryCorrectResponse[i].Short);
30495    }
30496
30497    return CMI5_RecordInteraction(
30498        strID,
30499        strResponse,
30500        blnCorrect,
30501        strCorrectResponse,
30502        strDescription,
30503        intWeighting,
30504        intLatency,
30505        strLearningObjectiveID,
30506        dtmTime,
30507        CMI5_INTERACTION_TYPE_SEQUENCING
30508    );
30509}
30510
30511function CMI5_RecordLikertInteraction (strID, response, blnCorrect, correctResponse, strDescription, intWeighting, intLatency, strLearningObjectiveID, dtmTime) {
30512    WriteToDebug(
30513        "In CMI5_RecordLikertInteraction strID=" + strID + ", response=" + response +
30514        ", blnCorrect=" + blnCorrect + ", correctResponse=" + correctResponse +
30515        ", strDescription=" + strDescription + ", intWeighting=" + intWeighting + ", intLatency=" + intLatency +
30516        ", strLearningObjectiveID=" + strLearningObjectiveID + ", dtmTime=" + dtmTime
30517    );
30518
30519    var strResponse = null,
30520        strCorrectResponse = "";
30521
30522    if (response !== null) {
30523         strResponse = (response.Long !== "" ? response.Long : response.Short);
30524    }
30525    if (correctResponse !== null) {
30526        strCorrectResponse = (correctResponse.Long !== "" ? correctResponse.Long : correctResponse.Short);
30527    }
30528
30529    return CMI5_RecordInteraction(
30530        strID,
30531        strResponse,
30532        blnCorrect,
30533        strCorrectResponse,
30534        strDescription,
30535        intWeighting,
30536        intLatency,
30537        strLearningObjectiveID,
30538        dtmTime,
30539        CMI5_INTERACTION_TYPE_LIKERT
30540    );
30541}
30542
30543function CMI5_RecordNumericInteraction(strID, strResponse, blnCorrect, strCorrectResponse, strDescription, intWeighting, intLatency, strLearningObjectiveID, dtmTime) {
30544    WriteToDebug(
30545        "In CMI5_RecordNumericInteraction strID=" + strID + ", strResponse=" + strResponse +
30546        ", blnCorrect=" + blnCorrect + ", strCorrectResponse=" + strCorrectResponse +
30547        ", strDescription=" + strDescription + ", intWeighting=" + intWeighting + ", intLatency=" + intLatency +
30548        ", strLearningObjectiveID=" + strLearningObjectiveID + ", dtmTime=" + dtmTime
30549    );
30550
30551    if (typeof strCorrectResponse !== "undefined" && strCorrectResponse !== null) {
30552        // cmi5 can handle numeric ranges or decimals. As long as we received one of those we are ok to continue
30553        if (! IsValidDecimalRange(strCorrectResponse) && ! IsValidDecimal(strCorrectResponse)) {
30554            WriteToDebug("Returning False - CMI5_RecordNumericInteraction received invali
30554d correct response (not a decimal or range), strCorrectResponse=" + strCorrectResponse);
30555            return false;
30556        }
30557    }
30558
30559    return CMI5_RecordInteraction(
30560        strID,
30561        strResponse,
30562        blnCorrect,
30563        strCorrectResponse,
30564        strDescription,
30565        intWeighting,
30566        intLatency,
30567        strLearningObjectiveID,
30568        dtmTime,
30569        CMI5_INTERACTION_TYPE_NUMERIC
30570    );
30571}
30572
30573//
30574// Previously recorded interactions have metadata that can be accessed
30575// by the following functions for some standards, implemented this via
30576// the state mechanism and a local cache, if the interation hasn't been
30577// previously recorded then the content should not expect to use these
30578//
30579function CMI5_CreateInteraction (
30580    strID,
30581    strResponse,
30582    blnCorrect,
30583    strCorrectResponse,
30584    strDescription,
30585    intWeighting,
30586    intLatency,
30587    strLearningObjectiveID,
30588    dtmTime,
30589    interactionType,
30590    strAlternateResponse,
30591    strAlternateCorrectResponse
30592) {
30593    CMI5_INTERACTIONS[strID] = {
30594        type: interactionType,
30595        timestamp: dtmTime.toJSON(),
30596        correctResponses: strCorrectResponse,
30597        learnerResponses: strResponse,
30598        weighting: intWeighting,
30599        result: blnCorrect,
30600        latency: intLatency,
30601        description: strDescription
30602    };
30603
30604    return;
30605}
30606
30607function CMI5_LoadInteraction (strInteractionId) {
30608    WriteToDebug("CMI5_LoadInteraction - strInteractionId = " + strInteractionId);
30609    var result;
30610
30611    if (typeof CMI5_INTERACTIONS[strInteractionId] !== "undefined") {
30612        WriteToDebug("    already locally cached, returning");
30613        return true;
30614    }
30615
30616    result = CMI5_RetrieveState(strInteractionId);
30617
30618    //
30619    // retrieve state doesn't provide a return value for detecting errors
30620    // so check to see if we have manually set errors (cause we shouldn't
30621    // unless the retrieve failed)
30622    //
30623    if (intCMI5Error) {
30624        WriteToDebug("    failed to retrieve state for interaction: " + strCMI5ErrorString);
30625        return false;
30626    }
30627
30628    if (result !== "" && typeof result.timestamp === "undefined") {
30629        // interaction was found but most likely failed to parse as JSON
30630        // which retrieveState in TinCanJS eats as an error, but make it
30631        // one here
30632        CMI5_SetErrorInfoManually(CMI5_ERROR_INTERACTION_INVALID_JSON, "Invalid JSON for interaction in State", "result= " + result);
30633        return false;
30634    }
30635
30636    if (result === "") {
30637        // interaction doesn't exist which is okay here,
30638        // just don't store anything in our local cache
30639        return true;
30640    }
30641
30642    CMI5_INTERACTIONS[strInteractionId] = result;
30643
30644    WriteToDebug("    cached interaction locally");
30645    return true;
30646}
30647
30648function CMI5_SaveInteraction (strInteractionId) {
30649    WriteToDebug("CMI5_SaveInteraction - strInteractionId = " + strInteractionId);
30650
30651    if (typeof CMI5_INTERACTIONS[strInteractionId] === "undefined") {
30652        CMI5_SetErrorInfoManually(CMI5_ERROR_UNRECOGNIZED_INTERACTION, "Cannot save unknown interaction: " + strInteractionId);
30653        return false;
30654    }
30655
30656    return CMI5_SaveState(strInteractionId, CMI5_INTERACTIONS[strInteractionId], "application/json");
30657}
30658
30659function CMI5_GetInteractionType(strInteractionID) {
30660    WriteToDebug("CMI5_GetInteractionType - strInteractionID = " + strInteractionID);
30661    var result;
30662
30663    CMI5_ClearErrorInfo();
30664
30665    result = CMI5_LoadInteraction(strInteractionID);
30666    if (! result) {
30667        WriteToDebug("Failed to load interaction: " + strInteractionID);
30668        CMI5_SetErrorInfoManually(CMI5_ERROR_INTERACTION_LOAD_FAILED, "Cannot get interaction type: " + strInteractionID, "Failed to load interaction: " + strCMI5ErrorString + " (" + strCMI5ErrorDiagnostic + ")");
30669        return false;
30670    }
30671
30672    if (typeof CMI5_INTERACTIONS[strInteractionID] !== "undefined") {
30673        return CMI5_INTERACTIONS[strInteractionID].type;
30674    }
30675
30676    return "";
30677}
30678
30679function CMI5_GetInteractionTimestamp(strInteractionID) {
30680    WriteToDebug("CMI5_GetInteractionTimestamp - strInteractionID = " + strInteractionID);
30681    var result;
30682
30683    CMI5_ClearErrorInfo();
30684
30685    result = CMI5_LoadInteraction(strInteractionID);
30686    if (! result) {
30687        WriteToDebug("Failed to load interaction: " + strInteractionID);
30688        CMI5_SetErrorInfoManually(CMI5_ERROR_INTERACTION_LOAD_FAILED, "Cannot get interaction timestamp: " + strInteractionID, "Failed to load interaction: " + strCMI5ErrorString + " (" + strCMI5ErrorDiagnostic + ")");
30689        return false;
30690    }
30691
30692    if (typeof CMI5_INTERACTIONS[strInteractionID] !== "undefined") {
30693        return new Date(CMI5_INTERACTIONS[strInteractionID].timestamp);
30694    }
30695
30696    return "";
30697}
30698
30699function CMI5_GetInteractionCorrectResponses(strInteractionID) {
30700    WriteToDebug("CMI5_GetInteractionCorrectResponses - strInteractionID = " + strInteractionID);
30701    var result,
30702        responses = [];
30703
30704    CMI5_ClearErrorInfo();
30705
30706    result = CMI5_LoadInteraction(strInteractionID);
30707    if (! result) {
30708        WriteToDebug("Failed to load interaction: " + strInteractionID);
30709        CMI5_SetErrorInfoManually(CMI5_ERROR_INTERACTION_LOAD_FAILED, "Cannot get interaction correct responses: " + strInteractionID, "Failed to load interaction: " + strCMI5ErrorString + " (" + strCMI5ErrorDiagnostic + ")");
30710        return false;
30711    }
30712
30713    if (typeof CMI5_INTERACTIONS[strInteractionID] !== "undefined") {
30714        responses = CMI5_INTERACTIONS[strInteractionID].correctResponses.split("[,]");
30715
30716        if (CMI5_INTERACTIONS[strInteractionID].type === CMI5_INTERACTION_TYPE_MATCHING) {
30717            responses = CMI5_ProcessResponseArray(responses);
30718        }
30719    }
30720
30721    return responses;
30722}
30723
30724function CMI5_GetInteractionLearnerResponses(strInteractionID) {
30725    WriteToDebug("CMI5_GetInteractionLearnerResponses - strInteractionID = " + strInteractionID);
30726    var result,
30727        responses = [];
30728
30729    CMI5_ClearErrorInfo();
30730
30731    result = CMI5_LoadInteraction(strInteractionID);
30732    if (! result) {
30733        WriteToDebug("Failed to load interaction: " + strInteractionID);
30734        CMI5_SetErrorInfoManually(CMI5_ERROR_INTERACTION_LOAD_FAILED, "Cannot get interaction learner responses: " + strInteractionID, "Failed to load interaction: " + strCMI5ErrorString + " (" + strCMI5ErrorDiagnostic + ")");
30735        return false;
30736    }
30737
30738    if (typeof CMI5_INTERACTIONS[strInteractionID] !== "undefined") {
30739        responses = CMI5_INTERACTIONS[strInteractionID].learnerResponses.split("[,]");
30740
30741        if (CMI5_INTERACTIONS[strInteractionID].type === CMI5_INTERACTION_TYPE_MATCHING) {
30742            responses = CMI5_ProcessResponseArray(responses);
30743        }
30744    }
30745
30746    return responses;
30747}
30748
30749function CMI5_ProcessResponseArray(responses) {
30750    var i;
30751
30752    for (i = 0; i < responses.length; i += 1) {
30753        responses[i] = CreateMatchingResponse(responses[i]);
30754    }
30755
30756    return responses;
30757}
30758
30759function CMI5_GetInteractionWeighting(strInteractionID) {
30760    WriteToDebug("CMI5_GetInteractionWeighting - strInteractionID = " + strInteractionID);
30761    var result;
30762
30763    CMI5_ClearErrorInfo();
30764
30765    result = CMI5_LoadInteraction(strInteractionID);
30766    if (! result) {
30767        WriteToDebug("Failed to load interaction: " + strInteractionID);
30768        CMI5_SetErrorInfoManually(CMI5_ERROR_INTERACTION_LOAD_FAILED, "Cannot get interaction weighting: " + strInteractionID, "Failed to load interaction: " + strCMI5ErrorString + " (" + strCMI5ErrorDiagnostic + ")");
30769        return false;
30770    }
30771
30772    if (typeof CMI5_INTERACTIONS[strInteractionID] !== "undefined") {
30773        return CMI5_INTERACTIONS[strInteractionID].weighting;
30774    }
30775
30776    return "";
30777}
30778
30779function CMI5_GetInteractionResult(strInteractionID) {
30780    WriteToDebug("CMI5_GetInteractionResult - strInteractionID = " + strInteractionID);
30781    var result;
30782
30783    CMI5_ClearErrorInfo();
30784
30785    result = CMI5_LoadInteraction(strInteractionID);
30786    if (! result) {
30787        WriteToDebug("Failed to load interaction: " + strInteractionID);
30788        CMI5_SetErrorInfoManually(CMI5_ERROR_INTERACTION_LOAD_FAILED, "Cannot get interaction result: " + strInteractionID, "Failed to load interaction: " + strCMI5ErrorString + " (" + strCMI5ErrorDiagnostic + ")");
30789        return false;
30790    }
30791
30792    if (typeof CMI5_INTERACTIONS[strInteractionID] !== "undefined") {
30793        return CMI5_INTERACTIONS[strInteractionID].result;
30794    }
30795
30796    return "";
30797}
30798
30799function CMI5_GetInteractionLatency(strInteractionID) {
30800    WriteToDebug("CMI5_GetInteractionLatency - strInteractionID = " + strInteractionID);
30801    var result;
30802
30803    CMI5_ClearErrorInfo();
30804
30805    result = CMI5_LoadInteraction(strInteractionID);
30806    if (! result) {
30807        WriteToDebug("Failed to load interaction: " + strInteractionID);
30808        CMI5_SetErrorInfoManually(CMI5_ERROR_INTERACTION_LOAD_FAILED, "Cannot get interaction latency: " + strInteractionID, "Failed to load interaction: " + strCMI5ErrorString + " (" + strCMI5ErrorDiagnostic + ")");
30809        return false;
30810    }
30811
30812    if (typeof CMI5_INTERACTIONS[strInteractionID] !== "undefined") {
30813        return CMI5_INTERACTIONS[strInteractionID].latency;
30814    }
30815
30816    return "";
30817}
30818
30819function CMI5_GetInteractionDescription(strInteractionID) {
30820    WriteToDebug("CMI5_GetInteractionDescription - strInteractionID = " + strInteractionID);
30821    var result;
30822
30823    CMI5_ClearErrorInfo();
30824
30825    result = CMI5_LoadInteraction(strInteractionID);
30826    if (! result) {
30827        WriteToDebug("Failed to load interaction: " + strInteractionID);
30828        CMI5_SetErrorInfoManually(CMI5_ERROR_INTERACTION_LOAD_FAILED, "Cannot get interaction description: " + strInteractionID, "Failed to load interaction: " + strCMI5ErrorString + " (" + strCMI5ErrorDiagnostic + ")");
30829        return false;
30830    }
30831
30832    if (typeof CMI5_INTERACTIONS[strInteractionID] !== "undefined") {
30833        return CMI5_INTERACTIONS[strInteractionID].description;
30834    }
30835
30836    return "";
30837}
30838
30839/*****************************
30840* Objectives
30841*****************************/
30842
30843function CMI5_SetObjectiveScore (strObjectiveID, intScore, intMaxScore, intMinScore) {
30844    WriteToDebug("In CMI5_SetObjectiveScore, strObjectiveID=" + strObjectiveID + ", intScore=" + intScore + ", intMaxScore=" + intMaxScore + ", intMinScore=" + intMinScore);
30845
30846    ClearErrorInfo();
30847    CMI5_SetErrorInfoManually(CMI5_ERROR_NOT_IMPLEMENTED, "SetObjectiveScore not implemented in cmi5", "");
30848
30849    return false;
30850}
30851
30852function CMI5_SetObjectiveDescription (strObjectiveID, strObjectiveDescription) {
30853    WriteToDebug("In CMI5_SetObjectiveDescription, strObjectiveDescription=" + strObjectiveDescription);
30854
30855    ClearErrorInfo();
30856    CMI5_SetErrorInfoManually(CMI5_ERROR_NOT_IMPLEMENTED, "SetObjectiveDescription not implemented in cmi5", "");
30857
30858    return false;
30859}
30860
30861function CMI5_SetObjectiveStatus (strObjectiveID, Lesson_Status) {
30862    WriteToDebug("In CMI5_SetObjectiveStatus strObjectiveID=" + strObjectiveID + ", Lesson_Status=" + Lesson_Status);
30863
30864    ClearErrorInfo();
30865    CMI5_SetErrorInfoManually(CMI5_ERROR_NOT_IMPLEMENTED, "SetObjectiveStatus not implemented in cmi5", "");
30866
30867    return false;
30868}
30869
30870function CMI5_SetObjectiveProgressMeasure () {
30871    WriteToDebug("In CMI5_SetObjectiveProgressMeasure");
30872
30873    ClearErrorInfo();
30874    CMI5_SetErrorInfoManually(CMI5_ERROR_NOT_IMPLEMENTED, "SetObjectiveProgressMeasure not implemented in cmi5", "");
30875
30876    return false;
30877}
30878
30879function CMI5_GetObjectiveScore (strObjectiveID) {
30880    WriteToDebug("In CMI5_GetObjectiveScore, strObjectiveID=" + strObjectiveID);
30881
30882    ClearErrorInfo();
30883    CMI5_SetErrorInfoManually(CMI5_ERROR_NOT_IMPLEMENTED, "GetObjectiveScore not implemented in cmi5", "");
30884
30885    return false;
30886}
30887
30888function CMI5_GetObjectiveDescription (strObjectiveID) {
30889    WriteToDebug("In CMI5_GetObjectiveDescription, strObjectiveID=" + strObjectiveID);
30890
30891    ClearErrorInfo();
30892    CMI5_SetErrorInfoManually(CMI5_ERROR_NOT_IMPLEMENTED, "GetObjectiveDescription not implemented in cmi5", "");
30893
30894    return false;
30895}
30896
30897function CMI5_GetObjectiveStatus (strObjectiveID) {
30898    WriteToDebug("In CMI5_GetObjectiveStatus, strObjectiveID=" + strObjectiveID);
30899
30900    ClearErrorInfo();
30901    CMI5_SetErrorInfoManually(CMI5_ERROR_NOT_IMPLEMENTED, "GetObjectiveStatus not implemented in cmi5", "");
30902
30903    return false;
30904}
30905
30906function CMI5_GetObjectiveProgressMeasure () {
30907    WriteToDebug("CMI5_GetObjectiveProgressMeasure");
30908
30909    ClearErrorInfo();
30910    CMI5_SetErrorInfoManually(CMI5_ERROR_NOT_IMPLEMENTED, "GetObjectiveProgressMeasure not implemented in cmi5", "");
30911
30912    return false;
30913}
30914
30915/*****************************
30916*  Status Management
30917*****************************/
30918
30919function CMI5_SetFailed () {
30920    WriteToDebug("In CMI5_SetFailed");
30921    var masteryScore,
30922        auActivityId,
30923        tempSt,
30924        i,
30925        st;
30926
30927    CMI5_ClearErrorInfo();
30928
30929    CMI5_ResetBlnStatusWasSetIfAppropriate();
30930
30931    if (cmi5.getLaunchMode() !== "Normal") {
30932        CMI5_SetErrorInfoManually(CMI5_ERROR_NON_NORMAL_MODE, "Failed to SetFailed", "Launch mode (" + cmi5.getLaunchMode() + ") not a recording mode");
30933        return false;
30934    }
30935
30936    //
30937    // check to see if success has already been permanently set
30938    //
30939    if (CMI5_COMMITTED_STATUS.success === true) {
30940        CMI5_SetErrorInfoManually(CMI5_ERROR_ALREADY_COMMITTED, "Failed to SetFailed", "Success status already committed");
30941        return false;
30942    }
30943
30944    //
30945    // if pending status already set nothing to do
30946    //
30947    if (CMI5_PENDING_STATUS.success === false) {
30948        // FEATURE: should we update the duration/timestamp of the pending statement?
30949        return true;
30950    }
30951
30952    //
30953    // check to see if a pending score has been set and if so make sure it works with masteryScore
30954    //
30955    masteryScore = cmi5.getMasteryScore();
30956    if (CMI5_PENDING_STATUS.score !== null && masteryScore !== null && CMI5_PENDING_STATUS.score.scaled >= masteryScore) {
30957        CMI5_SetErrorInfoManually(CMI5_ERROR_INVALID_STATUS_BASED_ON_SCORE, "Failed to SetFailed", "Pending score conflicts with failure");
30958        return false;
30959    }
30960
30961    //
30962    // if not permanently set but is pending set then need to remove passed statement
30963    //
30964    if (CMI5_PENDING_STATUS.success === true) {
30965        auActivityId = cmi5.getActivity().id;
30966
30967        for (i = 0; i < CMI5_STATEMENT_QUEUE.length; i += 1) {
30968            tempSt = CMI5_STATEMENT_QUEUE[i];
30969
30970            if (tempSt.target.id === auActivityId && tempSt.verb.id === CMI5_VERB_ID_PASSED) {
30971                CMI5_STATEMENT_QUEUE.splice(i, i + 1);
30972            }
30973        }
30974    }
30975    CMI5_PENDING_STATUS.success = false;
30976
30977    st = cmi5.failedStatement();
30978
30979    CMI5_SetDurationInStatement(st);
30980
30981    CMI5_STATEMENT_QUEUE.push(st);
30982
30983    return true;
30984}
30985
30986function CMI5_SetPassed () {
30987    WriteToDebug("In CMI5_SetPassed");
30988    var masteryScore,
30989        auActivityId,
30990        tempSt,
30991        i,
30992        st;
30993
30994    CMI5_ClearErrorInfo();
30995
30996    CMI5_ResetBlnStatusWasSetIfAppropriate();
30997
30998    if (cmi5.getLaunchMode() !== "Normal") {
30999        CMI5_SetErrorInfoManually(CMI5_ERROR_NON_NORMAL_MODE, "Failed to SetPassed", "Launch mode (" + cmi5.getLaunchMode() + ") not a recording mode");
31000        return false;
31001    }
31002
31003    //
31004    // check to see if success has already been permanently set
31005    //
31006    if (CMI5_COMMITTED_STATUS.success === true) {
31007        CMI5_SetErrorInfoManually(CMI5_ERROR_ALREADY_COMMITTED, "Failed to SetPassed", "Success status already committed");
31008        return false;
31009    }
31010
31011    //
31012    // if pending status already set nothing to do
31013    //
31014    if (CMI5_PENDING_STATUS.success === true) {
31015        // FEATURE: should we update the duration/timestamp of the pending statement?
31016        return true;
31017    }
31018
31019    //
31020    // check to see if a pending score has been set and if so make sure it works with masteryScore
31021    //
31022    masteryScore = cmi5.getMasteryScore();
31023    if (CMI5_PENDING_STATUS.score !== null && masteryScore !== null && CMI5_PENDING_STATUS.score.scaled < masteryScore) {
31024        CMI5_SetErrorInfoManually(CMI5_ERROR_INVALID_STATUS_BASED_ON_SCORE, "Failed to SetPassed", "Pending score conflicts with failure");
31025        return false;
31026    }
31027
31028    CMI5_PENDING_STATUS.success = true;
31029
31030    st = cmi5.passedStatement();
31031
31032    CMI5_SetDurationInStatement(st);
31033
31034    CMI5_STATEMENT_QUEUE.push(st);
31035
31036    return true;
31037}
31038
31039function CMI5_SetCompleted () {
31040    WriteToDebug("In CMI5_SetCompleted");
31041    var st;
31042
31043    CMI5_ClearErrorInfo();
31044
31045    if (cmi5.getLaunchMode() !== "Normal") {
31046        CMI5_SetErrorInfoManually(CMI5_ERROR_NON_NORMAL_MODE, "Failed to SetCompleted", "Launch mode (" + cmi5.getLaunchMode() + ") not a recording mode");
31047        return false;
31048    }
31049
31050    //
31051    // check to see if completion has already been permanently set
31052    //
31053    if (CMI5_COMMITTED_STATUS.completion !== null) {
31054        CMI5_SetErrorInfoManually(CMI5_ERROR_ALREADY_COMMITTED, "Failed to SetCompleted", "Completion status already committed");
31055        return false;
31056    }
31057
31058    //
31059    // if pending status already set nothing to do
31060    //
31061    if (CMI5_PENDING_STATUS.completion !== null) {
31062        // FEATURE: should we update the duration/timestamp of the pending statement?
31063        return true;
31064    }
31065    CMI5_PENDING_STATUS.completion = true;
31066
31067    st = cmi5.completedStatement();
31068
31069    CMI5_STATEMENT_QUEUE.push(st);
31070
31071    CMI5_SetDurationInStatement(st);
31072
31073    return true;
31074}
31075
31076function CMI5_ResetStatus () {
31077    WriteToDebug("In CMI5_ResetStatus");
31078    var auActivityId,
31079        i,
31080        tempSt;
31081
31082    CMI5_ClearErrorInfo();
31083
31084    if (cmi5.getLaunchMode() !== "Normal") {
31085        CMI5_SetErrorInfoManually(CMI5_ERROR_NON_NORMAL_MODE, "Failed to ResetStatus", "Launc
31085h mode (" + cmi5.getLaunchMode() + ") not a recording mode");
31086        return false;
31087    }
31088
31089    //
31090    // check to see if any previous status information has already
31091    // been committed, if it has then don't allow resetting the status
31092    //
31093    if (CMI5_COMMITTED_STATUS.completion !== null || CMI5_COMMITTED_STATUS.success === true) {
31094        CMI5_SetErrorInfoManually(CMI5_ERROR_ALREADY_COMMITTED, "Failed to ResetStatus", "Success and/or completion status has already been committed");
31095        return false;
31096    }
31097
31098    // FEATURE: make it configurable to do partial resets?
31099
31100    CMI5_PENDING_STATUS.completion = null;
31101    CMI5_PENDING_STATUS.success = null;
31102
31103    auActivityId = cmi5.getActivity().id;
31104    for (i = 0; i < CMI5_STATEMENT_QUEUE.length; i += 1) {
31105        tempSt = CMI5_STATEMENT_QUEUE[i];
31106
31107        if (
31108            tempSt.target.id = auActivityId
31109            &&
31110            (tempSt.verb.id === CMI5_VERB_ID_COMPLETED || tempSt.verb.id === CMI5_VERB_ID_PASSED || tempSt.verb.id === CMI5_VERB_ID_FAILED)
31111        ) {
31112            CMI5_STATEMENT_QUEUE.splice(i, i + 1);
31113        }
31114    }
31115
31116    return true;
31117}
31118
31119function CMI5_GetStatus () {
31120    WriteToDebug("In CMI5_GetStatus");
31121    var result,
31122        i,
31123        numAttempts = 0;
31124
31125    CMI5_ClearErrorInfo();
31126
31127    if (CMI5_COMMITTED_STATUS.success === true || CMI5_PENDING_STATUS.success === true) {
31128        result = LESSON_STATUS_PASSED;
31129    }
31130    else if (CMI5_COMMITTED_STATUS.success === false || CMI5_PENDING_STATUS.success === false) {
31131        result = LESSON_STATUS_FAILED;
31132    }
31133    else if (CMI5_COMMITTED_STATUS.completion === true || CMI5_PENDING_STATUS.completion === true) {
31134        result = LESSON_STATUS_COMPLETED;
31135    }
31136    else {
31137        for (i = 0; i < CMI5_COMMITTED_STATUS.launchModes.length; i += 1) {
31138            if (CMI5_COMMITTED_STATUS.launchModes[i] === "Normal") {
31139                numAttempts += 1;
31140            }
31141        }
31142
31143        if (numAttempts === 0) {
31144            result = LESSON_STATUS_BROWSED;
31145        }
31146        else {
31147            if (CMI5_ENTRY_MODE === ENTRY_FIRST_TIME) {
31148                result = LESSON_STATUS_NOT_ATTEMPTED;
31149            }
31150            else {
31151                result = LESSON_STATUS_INCOMPLETE;
31152            }
31153        }
31154    }
31155
31156    WriteToDebug("In CMI5_GetStatus - result = " + result);
31157    return result;
31158}
31159
31160function CMI5_GetPassingScore () {
31161    WriteToDebug("In CMI5_GetPassingScore");
31162    var result;
31163
31164    CMI5_ClearErrorInfo();
31165
31166    result = cmi5.getMasteryScore();
31167    if (result === null) {
31168        return 0;
31169    }
31170
31171    //
31172    // Driver's interface assumes an integer in the range of 0 to 100 for SCORM 1.2
31173    // conformance, so multiply by 100 (see comment in CMI5_SetScore why this is silly)
31174    //
31175    result = result * 100;
31176
31177    return result;
31178}
31179
31180/*****************************
31181*  Shared State Persistence
31182*****************************/
31183
31184function CMI5_DetectSSPSupport () {
31185    WriteToDebug("CMI5_DetectSSPSupport - implemented via state");
31186
31187    ClearErrorInfo();
31188
31189    return true;
31190}
31191
31192function CMI5_CreateDataBucket (strBucketId, intMinSize, intMaxSize) {
31193    WriteToDebug("CMI5_CreateDataBucket - strBucketId = " + strBucketId);
31194    var result;
31195
31196    ClearErrorInfo();
31197
31198    //
31199    // theoretically buckets are unique so make sure it doesn't already exist,
31200    // but it is possible this session hasn't laoded it yet, so make sure to
31201    // have fetched it first
31202    //
31203    result = CMI5_LoadBucket(strBucketId);
31204    if (! result) {
31205        WriteToDebug("Failed pre-existing bucket check, bucket can't be allocated");
31206        CMI5_SetErrorInfoManually(CMI5_ERROR_SSP_BUCKET_LOAD_FAILED, "Failed to create bucket: " + strBucketId, "pre-existence check load failure: " + strCMI5ErrorString + " (" + strCMI5ErrorDiagnostic + ")");
31207        return false;
31208    }
31209
31210    if (typeof CMI5_SSP_BUCKETS[strBucketId] !== "undefined") {
31211        WriteToDebug("Bucket already exists and can't be re-allocated.");
31212        CMI5_SetErrorInfoManually(CMI5_ERROR_SSP_BUCKET_ALREADY_EXISTS, "Failed to create bucket: " + strBucketId, "");
31213        return false;
31214    }
31215
31216    //
31217    // the bucket size is relatively limited and allowed to be set by the LMS,
31218    // an LRS is unlikely to have that small of a limit on state so we assume
31219    // that the max requested size is the amount that is always available
31220    //
31221    // the value stored in a bucket is always a string, so go ahead and initialize
31222    // it in an empty state
31223    //
31224    CMI5_SSP_BUCKETS[strBucketId] = {
31225        allocatedSpace: intMaxSize,
31226        contents: ""
31227    };
31228    return CMI5_SaveBucket(strBucketId);
31229}
31230
31231function CMI5_GetDataFromBucket (strBucketId) {
31232    WriteToDebug("CMI5_GetDataFromBucket - strBucketId = " + strBucketId);
31233    var result;
31234
31235    ClearErrorInfo();
31236
31237    result = CMI5_LoadBucket(strBucketId);
31238    if (! result) {
31239        WriteToDebug("Failed to load bucket: " + strBucketId);
31240        CMI5_SetErrorInfoManually(CMI5_ERROR_SSP_BUCKET_LOAD_FAILED, "Cannot get data from bucket: " + strBucketId, "Failed to load bucket: " + strCMI5ErrorString + " (" + strCMI5ErrorDiagnostic + ")");
31241        return false;
31242    }
31243
31244    if (typeof CMI5_SSP_BUCKETS[strBucketId] === "undefined") {
31245        CMI5_SetErrorInfoManually(CMI5_ERROR_UNRECOGNIZED_SSP_BUCKET, "Cannot get data from bucket: " + strBucketId, "unknown after load");
31246        return false;
31247    }
31248
31249    return CMI5_SSP_BUCKETS[strBucketId].contents;
31250}
31251
31252function CMI5_PutDataInBucket (strBucketId, strData, blnAppendToEnd) {
31253    WriteToDebug("CMI5_PutDataInBucket - strBucketId = " + strBucketId);
31254    var result;
31255
31256    ClearErrorInfo();
31257
31258    result = CMI5_LoadBucket(strBucketId);
31259    if (! result) {
31260        WriteToDebug("Failed to load bucket: " + strBucketId);
31261        CMI5_SetErrorInfoManually(CMI5_ERROR_SSP_BUCKET_LOAD_FAILED, "Cannot put data into bucket: " + strBucketId, "Failed to load bucket: " + strCMI5ErrorString + " (" + strCMI5ErrorDiagnostic + ")");
31262        return false;
31263    }
31264
31265    if (typeof CMI5_SSP_BUCKETS[strBucketId] === "undefined") {
31266        CMI5_SetErrorInfoManually(CMI5_ERROR_UNRECOGNIZED_SSP_BUCKET, "Cannot put data into bucket: " + strBucketId, "unknown after load");
31267        return false;
31268    }
31269
31270    if (blnAppendToEnd) {
31271        CMI5_SSP_BUCKETS[strBucketId].contents = CMI5_SSP_BUCKETS[strBucketId].contents + strData;
31272    }
31273    else {
31274        CMI5_SSP_BUCKETS[strBucketId].contents = strData;
31275    }
31276
31277    return CMI5_SaveBucket(strBucketId);
31278}
31279
31280function CMI5_GetBucketInfo (strBucketId) {
31281    WriteToDebug("CMI5_GetBucketInfo - strBucketId = " + strBucketId);
31282    var result;
31283
31284    ClearErrorInfo();
31285
31286    result = CMI5_LoadBucket(strBucketId);
31287    if (! result) {
31288        WriteToDebug("Failed to load bucket: " + strBucketId);
31289        CMI5_SetErrorInfoManually(CMI5_ERROR_SSP_BUCKET_LOAD_FAILED, "Cannot get info for bucket: " + strBucketId, "Failed to load bucket: " + strCMI5ErrorString + " (" + strCMI5ErrorDiagnostic + ")");
31290        return false;
31291    }
31292
31293    if (typeof CMI5_SSP_BUCKETS[strBucketId] === "undefined") {
31294        CMI5_SetErrorInfoManually(CMI5_ERROR_UNRECOGNIZED_SSP_BUCKET, "Cannot get info for bucket: " + strBucketId, "unknown after load");
31295        return false;
31296    }
31297
31298    return new SSPBucketSize(CMI5_SSP_BUCKETS[strBucketId].allocatedSpace, CMI5_SSP_BUCKETS[strBucketId].contents.length);
31299}
31300
31301function CMI5_LoadBucket (strBucketId) {
31302    WriteToDebug("CMI5_LoadBucket - strBucketId = " + strBucketId);
31303    var result;
31304
31305    if (typeof CMI5_SSP_BUCKETS[strBucketId] !== "undefined") {
31306        WriteToDebug("    already locally cached, returning");
31307        return true;
31308    }
31309
31310    result = CMI5_RetrieveState(CMI5_STATE_SSP_BUCKET_PREFIX + "-" + strBucketId);
31311
31312    //
31313    // retrieve state doesn't provide a return value for detecting errors
31314    // so check to see if we have manually set errors (cause we shouldn't
31315    // unless the retrieve failed)
31316    //
31317    if (intCMI5Error) {
31318        WriteToDebug("    failed to retrieve state for possible existing bucket: " + strCMI5ErrorString);
31319        return false;
31320    }
31321
31322    if (result !== "" && typeof result.allocatedSpace === "undefined") {
31323        // bucket was found but most likely failed to parse as JSON
31324        // which retrieveState in TinCanJS eats as an error, but make it
31325        // one here
31326        CMI5_SetErrorInfoManually(CMI5_ERROR_INTERACTION_INVALID_JSON, "Invalid JSON for bucket in State", "result= " + result);
31327        return false;
31328    }
31329
31330    if (result === "") {
31331        // bucket doesn't exist which is okay here,
31332        // just don't store anything in our local cache
31333        return true;
31334    }
31335
31336    CMI5_SSP_BUCKETS[strBucketId] = result;
31337
31338    WriteToDebug("    cached bucket locally");
31339    return true;
31340}
31341
31342function CMI5_SaveBucket (strBucketId) {
31343    WriteToDebug("CMI5_SaveBucket - strBucketId = " + strBucketId);
31344
31345    if (typeof CMI5_SSP_BUCKETS[strBucketId] === "undefined") {
31346        CMI5_SetErrorInfoManually(CMI5_ERROR_UNRECOGNIZED_SSP_BUCKET, "Cannot save unknown bucket: " + strBucketId);
31347        return false;
31348    }
31349
31350    return CMI5_SaveState(CMI5_STATE_SSP_BUCKET_PREFIX + "-" + strBucketId, CMI5_SSP_BUCKETS[strBucketId], "application/json");
31351}
31352
31353/*****************************
31354*  Miscellaneous
31355*****************************/
31356
31357function CMI5_SetDurationInStatement(st) {
31358    var duration;
31359
31360    if (CMI5_SESSION_DURATION !== null) {
31361        duration = CMI5_SESSION_DURATION;
31362    }
31363    else {
31364        duration = GetSessionAccumulatedTime();
31365    }
31366
31367    st.result = st.result || new TinCan.Result();
31368    st.result.duration = TinCan.Utils.convertMillisecondsToISO8601Duration(duration);
31369
31370    return;
31371}
31372
31373function CMI5_CreateValidIdentifier (str) {
31374    if (str === undefined || str === "" || str === null) {
31375        WriteToDebug("WARNING - returning invalid identifier, invalid argument provided: " + str);
31376        return "";
31377    }
31378
31379    //
31380    // switch URI to IRI mode temporarily, then capture the `encodeIRIComponent` function
31381    // off of the object for our local use, build and use our IRI and then return it
31382    // to its normal default state
31383    //
31384    // we need `encodeIRIComponent` to be able to encode the fragment which is otherwise
31385    // left alone by the URI normalization
31386    //
31387    URI.iri();
31388
31389    var uri = new URI(str),
31390        encodeIRIComponent = URI.encode,
31391        id;
31392
31393    if (! uri.is("absolute")) {
31394        //
31395        // getting here means it is not an absolute URI so we need to
31396        // manufacture one, to make it unique we'll add it to the AU's
31397        // Activity id
31398        //
31399        uri = new URI(cmi5.getActivity().id);
31400        if (uri.fragment() === "") {
31401            // no existing fragment, so make our identifier the fragment
31402            uri.hash(encodeIRIComponent(str));
31403        }
31404        else {
31405            // existing fragment, make our identifier part of it
31406            uri.hash(uri.fragment() + "-" + encodeIRIComponent(str));
31407        }
31408    }
31409
31410    // .iri is a "normalizer" (calls .normalize internally)
31411    uri.iri();
31412
31413    id = uri.toString();
31414
31415    URI.unicode();
31416
31417    return id;
31418}
31419
31420function CMI5_GetComments () {
31421    WriteToDebug("In CMI5_GetComments");
31422
31423    return CMI5_RetrieveState(CMI5_STATE_COMMENTS);
31424}
31425
31426function CMI5_WriteComment (strComment) {
31427    WriteToDebug("In CMI5_WriteComment - comment: " + strComment);
31428
31429    return CMI5_SaveState(CMI5_STATE_COMMENTS, strComment, "text/plain");
31430}
31431
31432function CMI5_GetLMSComments () {
31433    WriteToDebug("In CMI5_GetLMSComments - cmi5 does not support LMS comments");
31434
31435    ClearErrorInfo();
31436    CMI5_SetErrorInfoManually(CMI5_ERROR_NOT_IMPLEMENTED, "GetLMSComments not implemented in cmi5", "");
31437
31438    return "";
31439}
31440
31441function CMI5_SetNavigationRequest (strNavRequest) {
31442    WriteToDebug("CMI5_GetNavigationRequest - cmi5 does not support navigation requests, returning false");
31443
31444    ClearErrorInfo();
31445    CMI5_SetErrorInfoManually(CMI5_ERROR_NOT_IMPLEMENTED, "SetNavigationRequest not implemented in cmi5", "");
31446
31447    return false;
31448}
31449
31450function CMI5_GetNavigationRequest () {
31451    WriteToDebug("CMI5_GetNavigationRequest - cmi5 does not support navigation requests, returning false");
31452
31453    ClearErrorInfo();
31454    CMI5_SetErrorInfoManually(CMI5_ERROR_NOT_IMPLEMENTED, "GetNavigationRequest not implemented in cmi5", "");
31455
31456    return false;
31457}
31458
31459function CMI5_GetProgressMeasure () {
31460    WriteToDebug("CMI5_GetProgressMeasure - cmi5 does not support progress_measure, returning false");
31461
31462    ClearErrorInfo();
31463    CMI5_SetErrorInfoManually(CMI5_ERROR_NOT_IMPLEMENTED, "GetProgressMeasure not implemented in cmi5", "");
31464
31465    return false;
31466}
31467
31468function CMI5_SetProgressMeasure () {
31469    WriteToDebug("CMI5_SetProgressMeasure - cmi5 does not support progress_measure, returning false");
31470
31471    ClearErrorInfo();
31472    CMI5_SetErrorInfoManually(CMI5_ERROR_NOT_IMPLEMENTED, "SetProgressMeasure not implemented in cmi5", "");
31473
31474    return false;
31475}
31476
31477function CMI5_DisplayMessageOnTimeout () {
31478    WriteToDebug("In CMI5_DisplayMessageOnTimeout - cmi5 does not support MessageOnTimeout");
31479
31480    ClearErrorInfo();
31481    CMI5_SetErrorInfoManually(CMI5_ERROR_NOT_IMPLEMENTED, "DisplayMessageOnTimeout not implemented in cmi5", "");
31482
31483    return false;
31484}
31485
31486function CMI5_ExitOnTimeout () {
31487    WriteToDebug("In CMI5_ExitOnTimeout - cmi5 does not support ExitOnTimeout");
31488
31489    ClearErrorInfo();
31490    CMI5_SetErrorInfoManually(CMI5_ERROR_NOT_IMPLEMENTED, "ExitOnTimeout not implemented in cmi5", "");
31491
31492    return false;
31493}
31494
31495function CMI5_ResetBlnStatusWasSetIfAppropriate () {
31496    var isAlreadyComplete = CMI5_PENDING_STATUS.completion || CMI5_COMMITTED_STATUS.completion;
31497
31498    if (! isAlreadyComplete) {
31499        // Overriding the default behavior so cmi5 courses can treat completion and success completely independently
31500        WriteToDebug("Overriding blnStatusWasSet to false.");
31501        blnStatusWasSet = false;
31502    }
31503}
31504
31505/*****************************
31506*  Error Management
31507*****************************/
31508
31509function CMI5_ClearErrorInfo () {
31510    WriteToDebug("In CMI5_ClearErrorInfo");
31511
31512    intCMI5Error = CMI5_NO_ERROR;
31513    strCMI5ErrorString = "";
31514    strCMI5ErrorDiagnostic = "";
31515}
31516
31517function CMI5_SetErrorInfoManually (intNum, strString, strDiagnostic) {
31518    WriteToDebug("In CMI5_SetErrorInfoManually");
31519    WriteToDebug("ERROR-Num=" + intNum);
31520    WriteToDebug("      String=" + strString);
31521    WriteToDebug("      Diag=" + strDiagnostic);
31522
31523    intCMI5Error = intNum;
31524    strCMI5ErrorString = strString;
31525    strCMI5ErrorDiagnostic = strDiagnostic;
31526}
31527
31528function CMI5_GetLastError () {
31529    WriteToDebug("In CMI5_GetLastError");
31530
31531    if (intCMI5Error === CMI5_NO_ERROR) {
31532        WriteToDebug("Returning No Error");
31533        return NO_ERROR;
31534    }
31535
31536    WriteToDebug("Returning " + intCMI5Error);
31537    return intCMI5Error;
31538}
31539
31540function CMI5_GetLastErrorDesc () {
31541    WriteToDebug("In CMI5_GetLastErrorDesc, " + strCMI5ErrorString + "\n" + strCMI5ErrorDiagnostic);
31542    return strCMI5ErrorString + "\n" + strCMI5ErrorDiagnostic;
31543}
31544
31545function LMSStandardAPI (strStandard) {
31546    var interfaceMethods = [
31547            "Initialize",
31548            "Finish",
31549            "CommitData",
31550            "GetStudentID",
31551            "GetStudentName",
31552            "GetBookmark",
31553            "SetBookmark",
31554            "GetDataChunk",
31555            "SetDataChunk",
31556            "GetLaunchData",
31557            "GetComments",
31558            "WriteComment",
31559            "GetLMSComments",
31560            "GetAudioPlayPreference",
31561            "GetAudioVolumePreference",
31562            "SetAudioPreference",
31563            "SetLanguagePreference",
31564            "GetLanguagePreference",
31565            "SetSpeedPreference",
31566            "GetSpeedPreference",
31567            "SetTextPreference",
31568            "GetTextPreference",
31569            "GetPreviouslyAccumulatedTime",
31570            "SaveTime",
31571            "GetMaxTimeAllowed",
31572            "DisplayMessageOnTimeout",
31573            "ExitOnTimeout",
31574            "GetPassingScore",
31575            "SetScore",
31576            "GetScore",
31577            "GetScaledScore",
31578            "RecordTrueFalseInteraction",
31579            "RecordMultipleChoiceInteraction",
31580            "RecordFillInInteraction",
31581            "RecordMatchingInteraction",
31582            "RecordPerformanceInteraction",
31583            "RecordSequencingInteraction",
31584            "RecordLikertInteraction",
31585            "RecordNumericInteraction",
31586            "GetEntryMode",
31587            "GetLessonMode",
31588            "GetTakingForCredit",
31589            "SetObjectiveScore",
31590            "SetObjectiveStatus",
31591            "GetObjectiveScore",
31592            "GetObjectiveStatus",
31593            "SetObjectiveDescription",
31594            "GetObjectiveDescription",
31595            "SetFailed",
31596            "SetPassed",
31597            "SetCompleted",
31598            "ResetStatus",
31599            "GetStatus",
31600            "GetLastError",
31601            "GetLastErrorDesc",
31602            "GetInteractionType",
31603            "GetInteractionTimestamp",
31604            "GetInteractionCorrectResponses",
31605            "GetInteractionWeighting",
31606            "GetInteractionLearnerResponses",
31607            "GetInteractionResult",
31608            "GetInteractionLatency",
31609            "GetInteractionDescription",
31610            "CreateDataBucket",
31611            "GetDataFromBucket",
31612            "PutDataInBucket",
31613            "DetectSSPSupport",
31614            "GetBucketInfo",
31615            "GetProgressMeasure",
31616            "SetProgressMeasure",
31617            "SetPointBasedScore",
31618            "SetNavigationRequest",
31619            "GetNavigationRequest",
31620            "SetObjectiveProgressMeasure",
31621            "GetObjectiveProgressMeasure",
31622            "CreateValidIdentifier",
31623            "ConcedeControl"
31624        ],
31625        i;
31626
31627    WriteToDebug("In LMSStandardAPI strStandard=" + strStandard);
31628
31629    if (strStandard == "") {
31630        WriteToDebug("No standard specified, using NONE");
31631        strStandard = "NONE";
31632    }
31633
31634    this.Standard = strStandard;
31635
31636    for (i = 0; i < interfaceMethods.length; i += 1) {
31637        if (typeof window[strStandard + "_" + interfaceMethods[i]] !== "undefined") {
31638            this[interfaceMethods[i]] = window[strStandard + "_" + interfaceMethods[i]];
31639        }
31640        else {
31641            WriteToDebug("In LMSStandardAPI missing interface method=" + interfaceMethods[i]);
31642        }
31643    }
31644}
31645//API functions - each function validates inputs, then passes call to Standard function
31646
31647//global status variables
31648var blnCalledFinish = false;
31649var blnStandAlone = false;
31650var blnLoaded = false;
31651var blnReachedEnd = false;
31652var blnStatusWasSet = false;
31653var blnLmsPresent = false;
31654var blnUnloading = false;
31655
31656//time tracking variables
31657var dtmStart = null;
31658var dtmEnd = null;
31659var intAccumulatedMS = 0;
31660var blnOverrodeTime = false;
31661var intTimeOverrideMS = null;
31662
31663//debug info
31664var aryDebug = new Array();
31665var strDebug = "";
31666var winDebug;
31667
31668var intError = NO_ERROR;
31669var strErrorDesc = "";
31670
31671var objLMS = null;
31672
31673//public
31674function Start(){
31675
31676    var strStandAlone;
31677    var strShowInteractiveDebug;
31678    var objTempAPI = null;
31679    var aiccUrl = "";
31680    var endpoint = "";
31681    var cmi5FetchUrl = "";
31682
31683    WriteToDebug("<h1>SCORM Driver starting up</h1>");
31684    WriteToDebug("----------------------------------------");
31685    WriteToDebug("----------------------------------------");
31686    WriteToDebug("In Start - Version: " + VERSION + "  Last Modified=" + window.document.lastModified);
31687    WriteToDebug("Browser Info (" + navigator.appName + " " + navigator.appVersion + ")");
31688    WriteToDebug("URL: " + window.document.location.href);
31689    WriteToDebug("----------------------------------------");
31690    WriteToDebug("----------------------------------------");
31691
31692    ClearErrorInfo();
31693
31694    strStandAlone           = GetQueryStringValue("StandAlone", window.location.search);
31695    strShowInteractiveDebug = GetQueryStringValue("ShowDebug", window.location.search);
31696
31697    WriteToDebug("strStandAlone=" + strStandAlone + "  strShowInteractiveDebug=" + strShowInteractiveDebug);
31698
31699    if (ConvertStringToBoolean(strStandAlone)){
31700        WriteToDebug("Entering Stand Alone Mode");
31701        blnStandAlone = true;
31702    }
31703
31704    if (blnStandAlone){
31705        //allow NONE standard here to override config setting
31706        WriteToDebug("Using NONE Standard");
31707        objLMS = new LMSStandardAPI("NONE");
31708    }
31709    else{
31710        WriteToDebug("Standard From Configuration File - " + strLMSStandard);
31711
31712        if (strLMSStandard.toUpperCase() == "AUTO") {
31713            //to try to overcome some unsafe javascript warnings in AUTO mode,
31714            //let's check for AICC first:
31715            WriteToDebug("Searching for recognized querystring parameters");
31716            aiccUrl = GetQueryStringValue("AICC_URL", document.location.search);
31717            endpoint = GetQueryStringValue("endpoint", document.location.search);
31718            cmi5FetchUrl = GetQueryStringValue("fetch", document.location.search);
31719
31720            if (aiccUrl != null && aiccUrl != "") {
31721                WriteToDebug("Found AICC querystring parameters, using AICC");
31722                objLMS = new LMSStandardAPI("AICC");
31723                blnLmsPresent = true;
31724            }
31725            else if (endpoint != null && endpoint != "") {
31726                WriteToDebug("Found endpoint querystring parameter - checking cmi5 or Tin Can");
31727                if (cmi5FetchUrl != null && cmi5FetchUrl != "") {
31728                    WriteToDebug("Found fetch querystring parameter, using cmi5");
31729                    objLMS = new LMSStandardAPI("CMI5");
31730                    blnLmsPresent = true;
31731                }
31732                else {
31733                    WriteToDebug("Did not find fetch querystring parameter, using Tin Can");
31734                    objLMS = new LMSStandardAPI("TCAPI");
31735                    blnLmsPresent = true;
31736
31737                    // overwrite this so that tc-config.js is loaded for initialization below
31738                    strLMSStandard = "TCAPI";
31739                }
31740            }
31741            else {
31742                WriteToDebug("Auto-detecting standard - Searching for SCORM 2004 API");
31743                /*
31744                We wrap the API grabbers in try blocks because we don't know what kind of envrionment we were
31745                launched in (especially in AUTO mode). The LMS may have taken away the opener/parent window (like
31746                the Saba preview mode does in AICC) or there could be pages from different domains to deal with.
31747                */
31748                try{
31749                    objTempAPI = SCORM2004_GrabAPI();
31750                }
31751                catch (e){
31752                    WriteToDebug("Error grabbing 2004 API-" + e.name + ":" + e.message);
31753                }
31754                if (! (typeof(objTempAPI) == "undefined" || objTempAPI == null)){
31755                    WriteToDebug("Found SCORM 2004 API, using SCORM 2004");
31756                    objLMS = new LMSStandardAPI("SCORM2004");
31757                    blnLmsPresent = true;
31758                }
31759                else {
31760                    WriteToDebug("Searching for SCORM 1.2 API");
31761                    try{
31762                        objTempAPI = SCORM_GrabAPI();
31763                    }
31764                    catch (e){
31765                        WriteToDebug("Error grabbing 1.2 API-" + e.name + ":" + e.message);
31766                    }
31767
31768                    if (! (typeof(objTempAPI) == "undefined" || objTempAPI == null)) {
31769                        WriteToDebug("Found SCORM API, using SCORM");
31770                        objLMS = new LMSStandardAPI("SCORM");
31771                        blnLmsPresent = true;
31772                    }
31773                    else {
31774                        if (ALLOW_NONE_STANDARD === true) {
31775                            WriteToDebug("Could not determine standard, defaulting to Stand Alone");
31776                            objLMS = new LMSStandardAPI("NONE");
31777                        }
31778                        else {
31779                            WriteToDebug("Could not determine standard, Stand Alone is disabled in configuration");
31780                            DisplayError("Could not determine standard. SCORM, AICC, Tin Can, and C
31780MI5 APIs could not be found");
31781                            return;
31782                        }
31783                    }
31784                }
31785            }
31786        }
31787        else {
31788            WriteToDebug("Using Standard From Configuration File - " + strLMSStandard);
31789            objLMS = new LMSStandardAPI(strLMSStandard);
31790
31791            blnLmsPresent = true;   //set to true here and revert back to false if Initialize fails (in InitiaizeExecuted)
31792        }
31793    }
31794
31795    if (ConvertStringToBoolean(strShowInteractiveDebug) ||
31796            (  !(typeof(SHOW_DEBUG_ON_LAUNCH) == "undefined") && SHOW_DEBUG_ON_LAUNCH === true )
31797    ){
31798        WriteToDebug("Showing Interactive Debug Windows");
31799        ShowDebugWindow();
31800    }
31801
31802    WriteToDebug("Calling Standard Initialize");
31803
31804    if (strLMSStandard.toUpperCase() == "TCAPI"){
31805        // dynamically load the tc_config.js file before calling init;
31806        // load from up one directory so that the content developer
31807        // does not need to edit anything inside of scormdriver/ and
31808        // this puts tc-config.js at the same level as the tincan.xml
31809        loadScript("../tc-config.js", objLMS.Initialize);
31810    }else{
31811        objLMS.Initialize();
31812    }
31813
31814    TouchCloud();
31815
31816    return;
31817}
31818
31819
31820
31821function InitializeExecuted(blnSuccess, strErrorMessage){
31822
31823    WriteToDebug("In InitializeExecuted, blnSuccess=" + blnSuccess + ", strErrorMessage=" + strErrorMessage);
31824
31825    if (!blnSuccess){
31826        WriteToDebug("ERROR - LMS Initialize Failed");
31827        if (strErrorMessage == ""){
31828            strErrorMessage = "An Error Has Occurred";
31829        }
31830        blnLmsPresent = false;
31831        DisplayError(strErrorMessage);
31832        return;
31833    }
31834
31835    // AICC needs to check for completion here instead of on Initialize so the GetParam has time to finish processing
31836    if(objLMS.Standard=='AICC'){
31837        AICC_InitializeExecuted();
31838    }
31839
31840    // continue on for everyone else
31841
31842    blnLoaded = true;
31843    dtmStart = new Date();
31844
31845    LoadContent();
31846
31847    return;
31848}
31849
31850
31851//private - no need to call this directly, use "Finish", "Suspend" or "Timeout"
31852function ExecFinish(ExitType){
31853
31854    WriteToDebug("In ExecFinish, ExiType=" + ExitType);
31855
31856    ClearErrorInfo();
31857
31858    if (blnLoaded && ! blnCalledFinish ){
31859
31860        WriteToDebug("Haven't called finish before, finishing");
31861
31862        blnCalledFinish = true;
31863
31864        if (blnReachedEnd && (!EXIT_SUSPEND_IF_COMPLETED)){
31865            WriteToDebug("Reached End, overiding exit type to FINISH");
31866            ExitType = EXIT_TYPE_FINISH;
31867        }
31868
31869        if(EXIT_NORMAL_IF_PASSED == true && objLMS.GetStatus()==LESSON_STATUS_PASSED){
31870            WriteToDebug("Passed status and config value set, overiding exit type to FINISH");
31871            ExitType = EXIT_TYPE_FINISH;
31872        }
31873
31874        //MR - 5/31/05 - changed this call to happen in the SetSessionTime function so we can be sure it is always called
31875        //if (blnOverrodeTime){
31876        //  WriteToDebug("Overrode Time");
31877        //  objLMS.SaveTime(intTimeOverrideMS);
31878        //}
31879
31880        //DE - 5/6/2010 - we should only accumulate and save the time
31881        //                on exit if it wasn't set already using SetSessionTime
31882        if(!blnOverrodeTime){
31883            WriteToDebug("Did not override time");
31884            dtmEnd = new Date();
31885            AccumulateTime();
31886            objLMS.SaveTime(intAccumulatedMS);
31887        }
31888
31889        blnLoaded = false;
31890
31891        WriteToDebug("Calling LMS Finish");
31892        return objLMS.Finish(ExitType, blnStatusWasSet);
31893
31894    }
31895
31896    return true;
31897}
31898
31899
31900//Utilities
31901
31902//public
31903//tells child frames when the API is ready
31904function IsLoaded(){
31905    WriteToDebug("In IsLoaded, returning -" + blnLoaded);
31906    return blnLoaded;
31907}
31908
31909
31910//public
31911function WriteToDebug(strInfo){
31912
31913    if (blnDebug){
31914
31915        var dtm = new Date();
31916        var strLine;
31917
31918        strLine = aryDebug.length + ":" + dtm.toString() + " - " + strInfo;
31919
31920        aryDebug[aryDebug.length] = strLine;
31921
31922        if (winDebug && !winDebug.closed){
31923            winDebug.document.body.appendChild( winDebug.document.createTextNode(strLine) );
31924            winDebug.document.body.appendChild( winDebug.document.createElement("br") );
31925        }
31926
31927    }
31928    return;
31929}
31930
31931//public
31932function ShowDebugWindow(){
31933    var renderLog = function () {
31934            var i,
31935                len = aryDebug.length;
31936
31937            winDebug.document.body.innerHTML = "";
31938            for (i = 0; i < len; i += 1) {
31939                winDebug.document.body.appendChild( winDebug.document.createTextNode(aryDebug[i]) );
31940                winDebug.document.body.appendChild( winDebug.document.createElement("br") );
31941            }
31942        };
31943
31944    if (winDebug && !winDebug.closed){
31945        winDebug.close();
31946    }
31947
31948    winDebug = window.open("lms/blank.html", "Debug", "width=600,height=300,resizable,scrollbars");
31949
31950    if (winDebug === null) {
31951        alert("Debug window could not be opened, popup blocker in place?");
31952    }
31953    else {
31954        if (winDebug.addEventListener || winDebug.attachEvent) {
31955            winDebug[winDebug.addEventListener ? 'addEventListener' : 'attachEvent'](
31956                (winDebug.attachEvent ? 'on' : '') + 'load',
31957                renderLog,
31958                false
31959            );
31960        }
31961
31962        renderLog();
31963
31964        winDebug.document.close();
31965        winDebug.focus();
31966    }
31967
31968    return;
31969}
31970
31971//public
31972function DisplayError(strMessage){
31973
31974    var blnShowDebug;
31975
31976    WriteToDebug("In DisplayError, strMessage=" + strMessage);
31977
31978    blnShowDebug = confirm("An error has occurred:\n\n" + strMessage + "\n\nPress 'OK' to view debug information to send to technical support.");
31979
31980    if (blnShowDebug){
31981        ShowDebugWindow();
31982    }
31983
31984}
31985
31986
31987//public
31988//combines API error information with Standard's error information
31989function GetLastError(){
31990
31991    WriteToDebug("In GetLastError, intError=" + intError);
31992
31993    if (intError != NO_ERROR){
31994        WriteToDebug("Returning API Error");
31995        return intError;
31996    }
31997    else if (IsLoaded() && objLMS.GetLastError() != NO_ERROR){
31998        WriteToDebug("Returning LMS Error");
31999        return ERROR_LMS;
32000    }
32001
32002    WriteToDebug("Returning No Error");
32003    return NO_ERROR;
32004}
32005
32006function GetLastLMSErrorCode(){
32007
32008    WriteToDebug("In GetLastLMSErrorCode, intError=" + intError);
32009
32010    var LMSError = objLMS.GetLastError();
32011
32012    if (IsLoaded() && LMSError != NO_ERROR){
32013
32014
32015        WriteToDebug("Returning LMS Error: " + LMSError);
32016        return LMSError;
32017    }
32018
32019    WriteToDebug("Returning No Error");
32020    return NO_ERROR;
32021}
32022
32023//public
32024function GetLastErrorDesc(){
32025    WriteToDebug("In GetLastErrorDesc");
32026
32027    if (intError != NO_ERROR){
32028        WriteToDebug("Returning API Error - " + strErrorDesc);
32029        return strErrorDesc;
32030    }
32031    else if (IsLoaded() && objLMS.GetLastError() != NO_ERROR){
32032        WriteToDebug("Returning LMS Error");
32033        return objLMS.GetLastErrorDesc();
32034    }
32035
32036    WriteToDebug("Returning No Error");
32037    return "";
32038}
32039
32040//private
32041function SetErrorInfo(intErrorNumToSet, strErrorDescToSet){
32042
32043
32044    WriteToDebug("In SetErrorInfo - Num=" + intErrorNumToSet + " Desc=" + strErrorDescToSet);
32045
32046    intError = intErrorNumToSet;
32047    strErrorDesc = strErrorDescToSet;
32048}
32049
32050//private
32051function ClearErrorInfo(){
32052    WriteToDebug("In ClearErrorInfo");
32053
32054    var intError = NO_ERROR;
32055    var strErrorDesc = "";
32056}
32057
32058
32059//public
32060function CommitData(){
32061    WriteToDebug("In CommitData");
32062
32063    ClearErrorInfo();
32064
32065    if (! IsLoaded()){
32066        SetErrorInfo(ERROR_NOT_LOADED, "Cannot make calls to the LMS before calling Start");
32067        return false;
32068    }
32069
32070    //MR - 5/31/05 - We want to make sure this call happens someplace other than just in the onunload event, so periodically save the data as it accumulates
32071    if (! blnOverrodeTime){
32072        WriteToDebug("Did not override time, saving incremental time");
32073        dtmEnd = new Date();
32074        AccumulateTime();
32075        dtmStart = new Date();
32076        objLMS.SaveTime(intAccumulatedMS);
32077    }
32078
32079    return objLMS.CommitData();
32080}
32081
32082
32083//Finish functions
32084
32085//public
32086function Suspend(){
32087    WriteToDebug("In Suspend");
32088    ClearErrorInfo();
32089
32090    return ExecFinish(EXIT_TYPE_SUSPEND);
32091}
32092
32093//public
32094function Finish(){
32095    WriteToDebug("In Finish");
32096    ClearErrorInfo();
32097
32098    return ExecFinish(EXIT_TYPE_FINISH);
32099}
32100
32101//public
32102function TimeOut(){
32103    WriteToDebug("In TimeOut");
32104    ClearErrorInfo();
32105
32106    return ExecFinish(EXIT_TYPE_TIMEOUT);
32107}
32108
32109//public
32110function Unload(){
32111    WriteToDebug("In Unload");
32112    ClearErrorInfo();
32113
32114    blnUnloading = true;
32115
32116    return ExecFinish(DEFAULT_EXIT_TYPE);
32117}
32118
32119//public
32120function SetReachedEnd(){
32121    WriteToDebug("In SetReachedEnd");
32122    ClearErrorInfo();
32123
32124    if (! IsLoaded()){
32125        SetErrorInfo(ERROR_NOT_LOADED, "Cannot make calls to the LMS before calling Start");
32126        return false;
32127    }
32128
32129    if (blnStatusWasSet == false){
32130        objLMS.SetCompleted();
32131    }
32132
32133    blnReachedEnd = true;
32134
32135    return true;
32136}
32137
32138//public
32139function ConcedeControl(userAction)
32140{
32141    WriteToDebug("Conceding control with type: " + EXIT_BEHAVIOR);
32142    ClearErrorInfo();
32143
32144    //It's ok to always call Suspend in here and not Finish. The proper way to get an exit type
32145    //of Finish is to call SetReachedEnd before calling ConcedeControl. This will ensure that
32146    //the ExecFinish function always uses the ExitTypeFinish behavior (which is to set the status to completed
32147    //if no status was previously set)
32148
32149    if (typeof objLMS.ConcedeControl !== "undefined") {
32150        Suspend();
32151        return objLMS.ConcedeControl();
32152    }
32153
32154    if (userAction && objLMS.Standard === "AICC") {
32155        objLMS.actionConceded = true;
32156        Suspend();
32157        return;
32158    }
32159
32160    Suspend();
32161    PerformConcedeActions();
32162}
32163
32164function PerformConcedeActions() {
32165    var contentRoot = null;
32166    var urlBase = null;
32167
32168    switch (EXIT_BEHAVIOR)
32169    {
32170        case "SCORM_RECOMMENDED":
32171            contentRoot = SearchParentsForContentRoot();
32172
32173            if (contentRoot==window.top)
32174            {
32175                contentRoot.window.close();
32176            }
32177            else
32178            {
32179                if (contentRoot != null){
32180                    //DE - 10/26/2010 - Add ability to specify absolute URL for EXIT_TARGET
32181                    if(IsAbsoluteUrl(EXIT_TARGET)){
32182                        contentRoot.scormdriver_content.location.href = EXIT_TARGET;
32183                    } else {
32184                        urlBase = GetContentRootUrlBase(contentRoot);
32185                        contentRoot.scormdriver_content.location.href= urlBase + EXIT_TARGET;
32186                    }
32187                }
32188            }
32189            break;
32190        case "ALWAYS_CLOSE":
32191            window.close();
32192            break;
32193        case "ALWAYS_CLOSE_TOP":
32194            window.top.close();
32195            break;
32196        case "ALWAYS_CLOSE_PARENT":
32197            window.parent.close();
32198            break;
32199        case "NOTHING":
32200            break;
32201        case "REDIR_CONTENT_FRAME":
32202            contentRoot = SearchParentsForContentRoot();
32203            if (contentRoot != null){
32204                //DE - 10/26/2010 - Add ability to specify absolute URL for EXIT_TARGET
32205                if(IsAbsoluteUrl(EXIT_TARGET)){
32206                    contentRoot.scormdriver_content.location.href = EXIT_TARGET;
32207                } else {
32208                    urlBase = GetContentRootUrlBase(contentRoot);
32209                    contentRoot.scormdriver_content.location.href= urlBase + EXIT_TARGET;
32210                }
32211            }
32212            break;
32213    }
32214
32215    return true;
32216
32217}
32218
32219function GetContentRootUrlBase(contentRoot){
32220
32221    //Must remove the querystring before working with the url, or parameters that contain
32222    //a slash in them will break this (as with AICC_URL)
32223    var urlParts = contentRoot.location.href.split("?")[0].split("/");
32224    delete urlParts[urlParts.length - 1];
32225    contentRoot = urlParts.join("/");
32226    return contentRoot;
32227}
32228
32229function SearchParentsForContentRoot(){
32230
32231    var contentRoot = null;
32232    var wnd = window;
32233    var i=0;    //safety guard to prevent infinite loop
32234
32235    if (wnd.scormdriver_content){
32236        contentRoot = wnd;
32237        return contentRoot;
32238    }
32239
32240    while (contentRoot == null && wnd != window.top && (i++ < 100)){
32241        if (wnd.scormdriver_content){
32242            contentRoot = wnd;
32243            return contentRoot;
32244        }
32245        else{
32246            wnd = wnd.parent;
32247        }
32248    }
32249    WriteToDebug("Unable to locate content root");
32250    return null;
32251}
32252
32253
32254//Storing and retrieving data
32255
32256//public
32257function GetStudentID(){
32258    WriteToDebug("In GetStudentID");
32259    ClearErrorInfo();
32260
32261    if (! IsLoaded()){
32262        SetErrorInfo(ERROR_NOT_LOADED, "Cannot make calls to the LMS before calling Start");
32263        return "";
32264    }
32265
32266    return objLMS.GetStudentID();
32267
32268}
32269
32270//public
32271function GetStudentName(){
32272    WriteToDebug("In GetStudentName");
32273    ClearErrorInfo();
32274
32275    if (! IsLoaded()){
32276        SetErrorInfo(ERROR_NOT_LOADED, "Cannot make calls to the LMS before calling Start");
32277        return "";
32278    }
32279
32280    return objLMS.GetStudentName();
32281}
32282
32283//public
32284function GetBookmark(){
32285    WriteToDebug("In GetBookmark");
32286    ClearErrorInfo();
32287
32288    if (! IsLoaded()){
32289        SetErrorInfo(ERROR_NOT_LOADED, "Cannot make calls to the LMS before calling Start");
32290        return "";
32291    }
32292
32293    return objLMS.GetBookmark();
32294
32295}
32296
32297//public
32298function SetBookmark(strBookmark, strDesc){
32299    WriteToDebug("In SetBookmark - strBookmark=" + strBookmark + ", strDesc=" + strDesc);
32300    ClearErrorInfo();
32301
32302    if (! IsLoaded()){
32303        SetErrorInfo(ERROR_NOT_LOADED, "Cannot make calls to the LMS before calling Start");
32304        return false;
32305    }
32306
32307    return objLMS.SetBookmark(strBookmark, strDesc);
32308}
32309
32310//public
32311function GetDataChunk(){
32312    WriteToDebug("In GetDataChunk");
32313    ClearErrorInfo();
32314
32315    if (! IsLoaded()){
32316        SetErrorInfo(ERROR_NOT_LOADED, "Cannot make calls to the LMS before calling Start");
32317        return "";
32318    }
32319
32320    return objLMS.GetDataChunk();
32321}
32322
32323//public
32324function SetDataChunk(strData){
32325    WriteToDebug("In SetDataChunk strData=" + strData);
32326    ClearErrorInfo();
32327
32328    if (! IsLoaded()){
32329        SetErrorInfo(ERROR_NOT_LOADED, "Cannot make calls to the LMS before calling Start");
32330        return false;
32331    }
32332
32333    return objLMS.SetDataChunk(strData);
32334}
32335
32336//public
32337function GetLaunchData(){
32338    WriteToDebug("In GetLaunchData");
32339    ClearErrorInfo();
32340
32341    if (! IsLoaded()){
32342        SetErrorInfo(ERROR_NOT_LOADED, "Cannot make calls to the LMS before calling Start");
32343        return "";
32344    }
32345
32346    return objLMS.GetLaunchData();
32347}
32348
32349//public
32350function GetComments(){
32351
32352    var strCommentString;
32353    var aryComments;
32354    var i;
32355
32356    WriteToDebug("In GetComments");
32357
32358    ClearErrorInfo();
32359
32360    if (! IsLoaded()){
32361        SetErrorInfo(ERROR_NOT_LOADED, "Cannot make calls to the LMS before calling Start");
32362        return null;
32363    }
32364
32365    strCommentString = objLMS.GetComments();
32366
32367    WriteToDebug("strCommentString=" + strCommentString);
32368
32369    strCommentString = new String(strCommentString);
32370
32371    if (strCommentString != ""){
32372
32373        aryComments = strCommentString.split(" | ");
32374
32375        for (i=0; i < aryComments.length; i++){
32376            WriteToDebug("Returning Comment #" + i);
32377            aryComments[i] = new String(aryComments[i]);
32378            aryComments[i] = aryComments[i].replace(/\|\|/g, "|");
32379            WriteToDebug("Comment #" + i + "=" + aryComments[i]);
32380        }
32381    }
32382    else{
32383        aryComments = new Array(0);
32384    }
32385
32386    return aryComments;
32387}
32388
32389//public
32390function WriteComment(strComment){
32391
32392    var strExistingCommentString;
32393
32394    WriteToDebug("In WriteComment strComment=" + strComment);
32395
32396    ClearErrorInfo();
32397
32398    strComment = new String(strComment);
32399
32400    if (! IsLoaded()){
32401        SetErrorInfo(ERROR_NOT_LOADED, "Cannot make calls to the LMS before calling Start");
32402        return false;
32403    }
32404
32405    strComment = strComment.replace(/\|/g, "||");
32406
32407    strExistingCommentString = objLMS.GetComments();
32408
32409    if (strExistingCommentString != "" && strExistingCommentString != 'undefined'){
32410        strComment = strExistingCommentString + " | " + strComment;
32411    }
32412
32413    return objLMS.WriteComment(strComment);
32414}
32415
32416
32417//public
32418function GetLMSComments(){
32419    WriteToDebug("In GetLMSComments");
32420    ClearErrorInfo();
32421
32422    if (! IsLoaded()){
32423        SetErrorInfo(ERROR_NOT_LOADED, "Cannot make calls to the LMS before calling Start");
32424        return "";
32425    }
32426
32427    return objLMS.GetLMSComments();
32428}
32429
32430
32431//Preferences
32432
32433//public
32434function GetAudioPlayPreference(){
32435    WriteToDebug("In GetAudioPlayPreference");
32436    ClearErrorInfo();
32437
32438    if (! IsLoaded()){
32439        SetErrorInfo(ERROR_NOT_LOADED, "Cannot make calls to the LMS before calling Start");
32440        return PREFERENCE_DEFAULT;
32441    }
32442
32443    return objLMS.GetAudioPlayPreference();
32444}
32445
32446//public
32447//returns int 1-100
32448function GetAudioVolumePreference(){
32449    WriteToDebug("GetAudioVolumePreference");
32450    ClearErrorInfo();
32451
32452    if (! IsLoaded()){
32453        SetErrorInfo(ERROR_NOT_LOADED, "Cannot make calls to the LMS before calling Start");
32454        return 100;
32455    }
32456
32457    return objLMS.GetAudioVolumePreference();
32458}
32459
32460//public
32461//percent is int 1-100
32462//PlayPreference is On/Off
32463function SetAudioPreference(PlayPreference, intPercentOfMaxVolume){
32464
32465    WriteToDebug("In SetAudioPreference PlayPreference=" + PlayPreference + " intPercentOfMaxVolume=" + intPercentOfMaxVolume);
32466
32467    ClearErrorInfo();
32468
32469    if (! IsLoaded()){
32470        SetErrorInfo(ERROR_NOT_LOADED, "Cannot make calls to the LMS before calling Start");
32471        return false;
32472    }
32473
32474    if (PlayPreference != PREFERENCE_OFF &&
32475        PlayPreference != PREFERENCE_ON){
32476
32477        WriteToDebug("Error Invalid PlayPreference");
32478
32479        SetErrorInfo(ERROR_INVALID_PREFERENCE, "Invalid PlayPreference passed to SetAudioPreference, PlayPreference=" + PlayPreference);
32480
32481        return false;
32482    }
32483
32484    if ( ! ValidInteger(intPercentOfMaxVolume) ){
32485        WriteToDebug("Error Invalid PercentOfMaxVolume - not an integer");
32486        SetErrorInfo(ERROR_INVALID_NUMBER, "Invalid PercentOfMaxVolume passed to SetAudioPreference (not an integer), intPercentOfMaxVolume=" + intPercentOfMaxVolume);
32487        return false;
32488    }
32489
32490    intPercentOfMaxVolume = parseInt(intPercentOfMaxVolume, 10);
32491
32492    if (intPercentOfMaxVolume < 1 || intPercentOfMaxVolume > 100){
32493        WriteToDebug("Error Invalid PercentOfMaxVolume - out of range");
32494        SetErrorInfo(ERROR_INVALID_NUMBER, "Invalid PercentOfMaxVolume passed to SetAudioPreference (must be between 1 and 100), intPercentOfMaxVolume=" + intPercentOfMaxVolume);
32495        return false;
32496    }
32497
32498    WriteToDebug("Calling to LMS");
32499    return objLMS.SetAudioPreference(PlayPreference, intPercentOfMaxVolume);
32500
32501}
32502
32503//public
32504function GetLanguagePreference(){
32505    WriteToDebug("In GetLanguagePreference");
32506    ClearErrorInfo();
32507
32508    if (! IsLoaded()){
32509        SetErrorInfo(ERROR_NOT_LOADED, "Cannot make calls to the LMS before calling Start");
32510        return "";
32511    }
32512
32513    return objLMS.GetLanguagePreference();
32514}
32515
32516//public
32517function SetLanguagePreference(strLanguage){
32518    WriteToDebug("In SetLanguagePreference strLanguage=" + strLanguage);
32519    ClearErrorInfo();
32520
32521    if (! IsLoaded()){
32522        SetErrorInfo(ERROR_NOT_LOADED, "Cannot make calls to the LMS before calling Start");
32523        return false;
32524    }
32525
32526    return objLMS.SetLanguagePreference(strLanguage);
32527
32528}
32529
32530//public
32531function GetSpeedPreference(){
32532    WriteToDebug("In GetSpeedPreference");
32533    ClearErrorInfo();
32534
32535    if (! IsLoaded()){
32536        SetErrorInfo(ERROR_NOT_LOADED, "Cannot make calls to the LMS before calling Start");
32537        return 100;
32538    }
32539
32540    return objLMS.GetSpeedPreference();
32541}
32542
32543
32544//public
32545function SetSpeedPreference(intPercentOfMax){
32546    WriteToDebug("In SetSpeedPreference intPercentOfMax=" + intPercentOfMax);
32547    ClearErrorInfo();
32548
32549    if (! IsLoaded()){
32550        SetErrorInfo(ERROR_NOT_LOADED, "Cannot make calls to the LMS before calling Start");
32551        return false;
32552    }
32553
32554    if ( ! ValidInteger(intPercentOfMax) ){
32555        WriteToDebug("ERROR Invalid Percent of MaxSpeed, not an integer");
32556        SetErrorInfo(ERROR_INVALID_NUMBER, "Invalid PercentOfMaxSpeed passed to SetSpeedPreference (not an integer), intPercentOfMax=" + intPercentOfMax);
32557        return false;
32558    }
32559
32560    intPercentOfMax = parseInt(intPercentOfMax, 10);
32561
32562    if (intPercentOfMax < 0 || intPercentOfMax > 100){
32563        WriteToDebug("ERROR Invalid Percent of MaxSpeed, out of range");
32564        SetErrorInfo(ERROR_INVALID_NUMBER, "Invalid PercentOfMaxSpeed passed to SetSpeedPreference (must be between 1 and 100), intPercentOfMax=" + intPercentOfMax);
32565        return false;
32566    }
32567
32568    WriteToDebug("Calling to LMS");
32569    return objLMS.SetSpeedPreference(intPercentOfMax);
32570
32571}
32572
32573//public
32574function GetTextPreference(){
32575    WriteToDebug("In GetTextPreference");
32576    ClearErrorInfo();
32577
32578    if (! IsLoaded()){
32579        SetErrorInfo(ERROR_NOT_LOADED, "Cannot make calls to the LMS before calling Start");
32580        return false;
32581    }
32582
32583    return objLMS.GetTextPreference();
32584}
32585
32586//public
32587function SetTextPreference(intPreference){
32588    WriteToDebug("In SetTextPreference intPreference=" + intPreference);
32589    ClearErrorInfo();
32590
32591    if (! IsLoaded()){
32592        SetErrorInfo(ERROR_NOT_LOADED, "Cannot make calls to the LMS before calling Start");
32593        return false;
32594    }
32595
32596    if (intPreference != PREFERENCE_DEFAULT &&
32597        intPreference != PREFERENCE_OFF &&
32598        intPreference != PREFERENCE_ON){
32599        WriteToDebug("Error - Invalid Preference");
32600        SetErrorInfo(ERROR_INVALID_PREFERENCE, "Invalid Preference passed to SetTextPreference, intPreference=" + intPreference);
32601
32602        return false;
32603    }
32604
32605    return objLMS.SetTextPreference(intPreference);
32606
32607}
32608
32609
32610
32611
32612//Timing
32613
32614//public
32615function GetPreviouslyAccumulatedTime(){
32616    WriteToDebug("In GetPreviouslyAccumulatedTime");
32617    ClearErrorInfo();
32618
32619    if (! IsLoaded()){
32620        SetErrorInfo(ERROR_NOT_LOADED, "Cannot make calls to the LMS before calling Start");
32621        return 0;
32622    }
32623
32624    return objLMS.GetPreviouslyAccumulatedTime();
32625}
32626
32627
32628
32629//private
32630function AccumulateTime(){
32631    WriteToDebug("In AccumulateTime dtmStart=" + dtmStart + " dtmEnd=" + dtmEnd + " intAccumulatedMS=" + intAccumulatedMS);
32632    if (dtmEnd != null && dtmStart != null){
32633        WriteToDebug("Accumulating Time");
32634        intAccumulatedMS += (dtmEnd.getTime() - dtmStart.getTime());
32635        WriteToDebug("intAccumulatedMS=" + intAccumulatedMS);
32636    }
32637
32638}
32639
32640
32641
32642//public
32643function GetSessionAccumulatedTime(){
32644
32645    WriteToDebug("In GetSessionAccumulatedTime");
32646
32647    ClearErrorInfo();
32648
32649    WriteToDebug("Setting dtmEnd to now");
32650
32651    dtmEnd = new Date();
32652
32653    WriteToDebug("Accumulating Time");
32654
32655    AccumulateTime();
32656
32657    if (dtmStart != null){
32658        WriteToDebug("Resetting dtmStart");
32659        dtmStart = new Date();
32660    }
32661
32662    WriteToDebug("Setting dtmEnd to null");
32663    dtmEnd = null;
32664
32665    WriteToDebug("Returning " + intAccumulatedMS);
32666
32667    return intAccumulatedMS;
32668}
32669
32670
32671//public
32672function SetSessionTime(intMilliseconds){
32673
32674    WriteToDebug("In SetSessionTime");
32675
32676    ClearErrorInfo();
32677
32678    if ( ! ValidInteger(intMilliseconds)){
32679        WriteToDebug("ERROR parameter is not an integer");
32680        SetErrorInfo(ERROR_INVALID_NUMBER, "Invalid intMilliseconds passed to SetSessionTime (not an integer), intMilliseconds=" + intMilliseconds);
32681        return false;
32682    }
32683
32684    intMilliseconds = parseInt(intMilliseconds, 10);
32685
32686    if (intMilliseconds < 0){
32687        WriteToDebug("Error, parameter is less than 0");
32688        SetErrorInfo(ERROR_INVALID_NUMBER, "Invalid intMilliseconds passed to SetSessionTime (must be greater than 0), intMilliseconds=" + intMilliseconds);
32689        return false;
32690    }
32691
32692    blnOverrodeTime = true;
32693    intTimeOverrideMS = intMilliseconds;
32694
32695    //MR - 5/31/05 - added this immediate call because in AICC the onunload call isn't always reliable
32696    objLMS.SaveTime(intTimeOverrideMS);
32697
32698    return true;
32699}
32700
32701//public
32702function PauseTimeTracking(){
32703
32704    WriteToDebug("In PauseTimeTracking");
32705
32706    ClearErrorInfo();
32707
32708    WriteToDebug("Setting dtmEnd to now");
32709    dtmEnd = new Date();
32710
32711    WriteToDebug("Accumulating Time");
32712    AccumulateTime();
32713
32714    WriteToDebug("Setting Start and End times to null");
32715    dtmStart = null;
32716    dtmEnd = null;
32717
32718    return true;
32719}
32720
32721//public
32722//note in docs - can be used to start tracking at a point other than beginning
32723function ResumeTimeTracking(){
32724
32725    WriteToDebug("In ResumeTimeTracking");
32726
32727    ClearErrorInfo();
32728
32729    WriteToDebug("Setting dtmStart to now");
32730
32731    dtmStart = new Date();
32732
32733    return true;
32734
32735}
32736
32737
32738//public
32739function GetMaxTimeAllowed(){
32740
32741    WriteToDebug("In GetMaxTimeAllowed");
32742
32743    ClearErrorInfo();
32744
32745    if (! IsLoaded()){
32746        SetErrorInfo(ERROR_NOT_LOADED, "Cannot make calls to the LMS before calling Start");
32747        return MAX_CMI_TIME;
32748    }
32749
32750    return objLMS.GetMaxTimeAllowed();
32751
32752}
32753
32754
32755//public
32756function DisplayMessageOnTimeout(){
32757
32758    WriteToDebug("In DisplayMessageOnTimeOut");
32759
32760    ClearErrorInfo();
32761
32762    if (! IsLoaded()){
32763        SetErrorInfo(ERROR_NOT_LOADED, "Cannot make calls to the LMS before calling Start");
32764        return false;
32765    }
32766
32767    return objLMS.DisplayMessageOnTimeout();
32768
32769}
32770
32771//public
32772function ExitOnTimeout(){
32773
32774    WriteToDebug("In ExitOnTimeOut");
32775
32776    ClearErrorInfo();
32777
32778    if (! IsLoaded()){
32779        SetErrorInfo(ERROR_NOT_LOADED, "Cannot make calls to the LMS before calling Start");
32780        return false;
32781    }
32782
32783    return objLMS.ExitOnTimeout();
32784
32785}
32786
32787
32788//Testing
32789
32790//public
32791function GetPassingScore(){
32792    WriteToDebug("In GetPassingScore");
32793    ClearErrorInfo();
32794
32795    if (! IsLoaded()){
32796        SetErrorInfo(ERROR_NOT_LOADED, "Cannot make calls to the LMS before calling Start");
32797        return 0;
32798    }
32799
32800    return objLMS.GetPassingScore();
32801
32802}
32803
32804//public
32805function GetScore(){
32806
32807    WriteToDebug("In GetScore");
32808    ClearErrorInfo();
32809
32810    if (! IsLoaded()){
32811        SetErrorInfo(ERROR_NOT_LOADED, "Cannot make calls to the LMS before calling Start");
32812        return 0;
32813    }
32814
32815    return objLMS.GetScore();
32816
32817}
32818
32819//public
32820function GetScaledScore(){
32821
32822    WriteToDebug("In GetScaledScore");
32823    ClearErrorInfo();
32824
32825    if (! IsLoaded()){
32826        SetErrorInfo(ERROR_NOT_LOADED, "Cannot make calls to the LMS before calling Start");
32827        return 0;
32828    }
32829
32830    return objLMS.GetScaledScore();
32831
32832}
32833
32834//public
32835function SetScore(intScore, intMaxScore, intMinScore){
32836
32837    WriteToDebug("In SetScore, intScore=" + intScore + ", intMaxScore=" + intMaxScore + ", intMinScore=" + intMinScore);
32838
32839    ClearErrorInfo();
32840
32841    if (! IsLoaded()){
32842        SetErrorInfo(ERROR_NOT_LOADED, "Cannot make calls to the LMS before calling Start");
32843        return false;
32844    }
32845
32846    if (! IsValidDecimal(intScore)){
32847        WriteToDebug("ERROR - intScore not a valid decimal");
32848        SetErrorInfo(ERROR_INVALID_NUMBER, "Invalid Score passed to SetScore (not a valid decimal), intScore=" + intScore);
32849        return false;
32850    }
32851
32852    if (! IsValidDecimal(intMaxScore)){
32853        WriteToDebug("ERROR - intMaxScore not a valid decimal");
32854        SetErrorInfo(ERROR_INVALID_NUMBER, "Invalid Max Score passed to SetScore (not a valid decimal), intMaxScore=" + intMaxScore);
32855        return false;
32856    }
32857
32858    if (! IsValidDecimal(intMinScore)){
32859        WriteToDebug("ERROR - intMinScore not a valid decimal");
32860        SetErrorInfo(ERROR_INVALID_NUMBER, "Invalid Min Score passed to SetScore (not a valid decimal), intMinScore=" + intMinScore);
32861        return false;
32862    }
32863
32864    WriteToDebug("Converting SCORES to floats");
32865    intScore = parseFloat(intScore);
32866    intMaxScore = parseFloat(intMaxScore);
32867    intMinScore = parseFloat(intMinScore);
32868
32869    //check the ranges for SCORM 1.2
32870    if(strLMSStandard == 'SCORM')
32871    {
32872        WriteToDebug("DEBUG - SCORM 1.2 so checking max score length");
32873        if (intScore < 0 || intScore > 100){
32874            WriteToDebug("ERROR - intScore out of range");
32875            SetErrorInfo(ERROR_INVALID_NUMBER, "Invalid Score passed to SetScore (must be between 0-100), intScore=" + intScore);
32876            return false;
32877        }
32878
32879        if (intMaxScore < 0 || intMaxScore > 100){
32880            WriteToDebug("ERROR - intMaxScore out of range");
32881            SetErrorInfo(ERROR_INVALID_NUMBER, "Invalid Max Score passed to SetScore (must be between 0-100), intMaxScore=" + intMaxScore);
32882            return false;
32883        }
32884
32885        if (intMinScore < 0 || intMinScore > 100){
32886            WriteToDebug("ERROR - intMinScore out of range");
32887            SetErrorInfo(ERROR_INVALID_NUMBER, "Invalid Min Score passed to SetScore (must be between 0-100), intMinScore=" + intMinScore);
32888            return false;
32889        }
32890    }
32891
32892    if (SCORE_CAN_ONLY_IMPROVE === true){
32893
32894        var previousScore = GetScore();
32895
32896        if (previousScore != null && previousScore != "" && previousScore > intScore){
32897            WriteToDebug("Previous score was greater than new score, configuration only allows scores to improve, returning.");
32898            return true;
32899        }
32900    }
32901
32902    WriteToDebug("Calling to LMS");
32903    return objLMS.SetScore(intScore, intMaxScore, intMinScore);
32904
32905}
32906
32907//public
32908function SetPointBasedScore(intScore, intMaxScore, intMinScore){
32909
32910    WriteToDebug("In SetPointBasedScore, intScore=" + intScore + ", intMaxScore=" + intMaxScore + ", intMinScore=" + intMinScore);
32911
32912    ClearErrorInfo();
32913
32914    if (! IsLoaded()){
32915        SetErrorInfo(ERROR_NOT_LOADED, "Cannot make calls to the LMS before calling Start");
32916        return false;
32917    }
32918
32919    if (! IsValidDecimal(intScore)){
32920        WriteToDebug("ERROR - intScore not a valid decimal");
32921        SetErrorInfo(ERROR_INVALID_NUMBER, "Invalid Score passed to SetScore (not a valid decimal), intScore=" + intScore);
32922        return false;
32923    }
32924
32925    if (! IsValidDecimal(intMaxScore)){
32926        WriteToDebug("ERROR - intMaxScore not a valid decimal");
32927        SetErrorInfo(ERROR_INVALID_NUMBER, "Invalid Max Score passed to SetScore (not a valid decimal), intMaxScore=" + intMaxScore);
32928        return false;
32929    }
32930
32931    if (! IsValidDecimal(intMinScore)){
32932        WriteToDebug("ERROR - intMinScore not a valid decimal");
32933        SetErrorInfo(ERROR_INVALID_NUMBER, "Invalid Min Score passed to SetScore (not a valid decimal), intMinScore=" + intMinScore);
32934        return false;
32935    }
32936
32937    WriteToDebug("Converting SCORES to floats");
32938    intScore = parseFloat(intScore);
32939    intMaxScore = parseFloat(intMaxScore);
32940    intMinScore = parseFloat(intMinScore);
32941
32942    //check the ranges for SCORM 1.2
32943    if(strLMSStandard == 'SCORM')
32944    {
32945        if (intScore < 0 || intScore > 100){
32946            WriteToDebug("ERROR - intScore out of range");
32947            SetErrorInfo(ERROR_INVALID_NUMBER, "Invalid Score passed to SetScore (must be between 0-100), intScore=" + intScore);
32948            return false;
32949        }
32950
32951        if (intMaxScore < 0 || intMaxScore > 100){
32952            WriteToDebug("ERROR - intMaxScore out of range");
32953            SetErrorInfo(ERROR_INVALID_NUMBER, "Invalid Max Score passed to SetScore (must be between 0-100), intMaxScore=" + intMaxScore);
32954            return false;
32955        }
32956
32957        if (intMinScore < 0 || intMinScore > 100){
32958            WriteToDebug("ERROR - intMinScore out of range");
32959            SetErrorInfo(ERROR_INVALID_NUMBER, "Invalid Min Score passed to SetScore (must be between 0-100), intMinScore=" + intMinScore);
32960            return false;
32961        }
32962    }
32963
32964    if (SCORE_CAN_ONLY_IMPROVE === true){
32965
32966        var previousScore = GetScore();
32967
32968        if (previousScore != null && previousScore != "" && previousScore > intScore){
32969            WriteToDebug("Previous score was greater than new score, configuration only allows scores to improve, returning.");
32970            return true;
32971        }
32972    }
32973
32974    WriteToDebug("Calling to LMS");
32975    return objLMS.SetPointBasedScore(intScore, intMaxScore, intMinScore);
32976
32977}
32978
32979
32980//class to hold a short and long version of an identifier for an interaction response
32981function CreateResponseIdentifier(strShort, strLong){
32982    // manipulate the short identifier into something that will work
32983    strShort = strShort.replace(/\W|_/g, '');
32984    strShort = strShort.length > 0 ? strShort.substr(0, 1) : 'z';
32985
32986    //validate that the short id is a single alphanumeric character
32987
32988    if (strShort.replace(" ", "") == ""){
32989        WriteToDebug("Short Identifier is empty");
32990        SetErrorInfo(ERROR_INVALID_ID, "Invalid short identifier, strShort=" + strShort);
32991        return false;
32992    }
32993
32994    if (strShort.length != 1){
32995        WriteToDebug("ERROR - Short Identifier  not 1 character");
32996        SetErrorInfo(ERROR_INVALID_ID, "Invalid short identifier, strShort=" + strShort);
32997        return false;
32998    }
32999
33000    if ( ! IsAlphaNumeric(strShort) ){
33001        WriteToDebug("ERROR - Short Identifier  not alpha numeric");
33002        SetErrorInfo(ERROR_INVALID_ID, "Invalid short identifier, strShort=" + strShort);
33003        return false;
33004    }
33005
33006    //convert the short identifier to lower case because it is unclear in the SCORM 1.2 spec whether or not
33007    //the response is allowed to be upper case and some LMS's may only allow lower case
33008    strShort = strShort.toLowerCase();
33009
33010    strLong = CreateValidIdentifier(strLong);
33011
33012
33013    return new ResponseIdentifier(strShort, strLong);
33014}
33015
33016
33017
33018
33019function ResponseIdentifier(strShort, strLong){
33020    this.Short = new String(strShort);
33021    this.Long = new String(strLong);
33022
33023    this.toString = function (){
33024        return "[Response Identifier " + this.Short + ", " + this.Long + "]";
33025    };
33026}
33027
33028//Represents a response to a matching interaction
33029//Contains two values, a source and a target, each of which can be a string or a ResponseIdentifier
33030function MatchingResponse(source, target){
33031
33032    if (source.constructor == String){
33033        source = CreateResponseIdentifier(source, source);
33034    }
33035
33036    if (target.constructor == String){
33037        target = CreateResponseIdentifier(target, target);
33038    }
33039
33040    this.Source = source;
33041    this.Target = target;
33042
33043    this.toString = function (){
33044        return "[Matching Response " + this.Source + ", " + this.Target + "]";
33045    };
33046}
33047
33048function CreateMatchingResponse(pattern)
33049{
33050    var aryPairs = new Array();
33051    var aryEachPair = new Array();
33052    pattern = new String(pattern);
33053
33054
33055    aryPairs = pattern.split("[,]");
33056
33057    for(var i=0; i<aryPairs.length; i++)
33058    {
33059        var thisPair = new String(aryPairs[i]);
33060        aryEachPair = thisPair.split("[.]");
33061        WriteToDebug("Matching Response [" + i + "]  source: " + aryEachPair[0] + "  target: " + aryEachPair[1]);
33062        aryPairs[i] = new MatchingResponse(aryEachPair[0], aryEachPair[1]);
33063    }
33064
33065    WriteToDebug("pattern: " + pattern + " becomes " + aryPairs[0]);
33066
33067    if (aryPairs.length==0) return aryPairs[0];
33068    else return aryPairs;
33069}
33070
33071/**
33072 * Convert this string into something that would be a valid identifier in this
33073 * learning standard.
33074 */
33075function CreateValidIdentifier(str) {
33076    return objLMS.CreateValidIdentifier(str);
33077}
33078
33079function CreateUriIdentifier(str, iri) {
33080    // I'm not sure this is valid, in any learning standard, but this is what
33081    // we had in earlier versions of driver
33082    if (str === undefined || str === null || str === "")
33083    {
33084        return "";
33085    }
33086
33087    str = Trim(str);
33088
33089    var uri = new URI(str);
33090
33091    // note that URNs return true here
33092    if (!uri.is('absolute'))
33093    {
33094        // If we're here, then the string we received probably wasn't a URN. So
33095        // let's attempt to create one in the 'scormdriver' namespace.
33096        str = 'urn:scormdriver:' + encodeURIComponent(str);
33097        uri = new URI(str);
33098    }
33099
33100    uri.normalize();
33101
33102    if (iri) {
33103        uri.iri();
33104    }
33105
33106    return uri.toString();
33107}
33108
33109//make sure all the criteria are met to allow this value to be an identifier
33110function CreateValidIdentifierLegacy(str){
33111    if (str != null || str != "") {
33112        str = new String(str);
33113        str = Trim(str);
33114
33115        //if the string starts with "urn:" then it has special requirements
33116        if (str.toLowerCase().indexOf("urn:") == 0) {
33117            str = str.substr(4);
33118        }
33119
33120        //urns may only contain the following characters: letters, numbers - ( ) + . : = @ ; $ _ ! * ' %
33121        //if anything else is found, replace it with _
33122        str = str.replace(/[^\w\-\(\)\+\.\:\=\@\;\$\_\!\*\'\%]/g, "_");
33123
33124        return str;
33125    }
33126
33127    return "";
33128}
33129
33130function Trim(str){
33131    str = str + '';
33132    str = str.replace(/^\s*/, "");
33133    str = str.replace(/\s*$/, "");
33134    return str;
33135}
33136
33137
33138
33139function RecordTrueFalseInteraction(strID, blnResponse, blnCorrect, blnCorrectResponse, strDescription, intWeighting, intLatency, strLearningObjectiveID){
33140
33141    //clean the ID if needed
33142    strID = CreateValidIdentifier(strID);
33143    strLearningObjectiveID = CreateValidIdentifier(strLearningObjectiveID);
33144
33145    WriteToDebug("In RecordTrueFalseInteraction strID=" + strID + ", blnResponse=" + blnResponse +
33146                    ", blnCorrect=" + blnCorrect + ", blnCorrectResponse=" + blnCorrectResponse +
33147                    ", strDescription=" + strDescription + ", intWeighting=" + intWeighting + ", intLatency=" + intLatency + ", strLearningObjectiveID=" + strLearningObjectiveID);
33148
33149
33150    if ( !(typeof(DO_NOT_REPORT_INTERACTIONS) == "undefined") && DO_NOT_REPORT_INTERACTIONS === true){
33151        WriteToDebug("Configuration specifies interactions should not be reported, exiting.");
33152        return true;
33153    }
33154
33155    ClearErrorInfo();
33156
33157    if (! IsLoaded()){
33158        SetErrorInfo(ERROR_NOT_LOADED, "Cannot make calls to the LMS before calling Start");
33159        return false;
33160    }
33161
33162    //blnResponse and blnCorrectResponse must be valid boolean values
33163    if ((blnResponse != true && blnResponse != false && blnResponse !== null) || (blnResponse === null && ! ALLOW_INTERACTION_NULL_LEARNER_RESPONSE)){
33164        SetErrorInfo(ERROR_INVALID_INTERACTION_RESPONSE, "The Response parameter must be a valid boolean value.");
33165        return false;
33166    }
33167    if (blnCorrectResponse != null && blnCorrectResponse != true && blnCorrectResponse != false){
33168        SetErrorInfo(ERROR_INVALID_INTERACTION_RESPONSE, "The Correct Response parameter must be a valid boolean value or null.");
33169        return false;
33170    }
33171
33172    var dtmTime = new Date();
33173
33174    WriteToDebug("Calling to LMS");
33175    return objLMS.RecordTrueFalseInteraction(strID, blnResponse, blnCorrect, blnCorrectResponse, strDescription, intWeighting, intLatency, strLearningObjectiveID, dtmTime);
33176}
33177
33178//public
33179//strResponse and strCorrectResponse can be either:
33180//  -a single character alphanumeric string representing the response
33181//  -a single ResponseIdentifier object
33182//  -an array of ResponseIdentifier objects representing multiple selections that must be/were made
33183
33184function RecordMultipleChoiceInteraction(strID, response, blnCorrect, correctResponse, strDescription, intWeighting, intLatency, strLearningObjectiveID){
33185
33186    //clean the ID if needed
33187    strID = CreateValidIdentifier(strID);
33188    strLearningObjectiveID = CreateValidIdentifier(strLearningObjectiveID);
33189
33190    WriteToDebug("In RecordMultipleChoiceInteraction strID=" + strID + ", response=" + response +
33191                    ", blnCorrect=" + blnCorrect + ", correctResponse=" + correctResponse +
33192                    ", strDescription=" + strDescription + ", intWeighting=" + intWeighting + ", intLatency=" + intLatency + ", strLearningObjectiveID=" + strLearningObjectiveID);
33193
33194    if (!(typeof(DO_NOT_REPORT_INTERACTIONS) == "undefined") && DO_NOT_REPORT_INTERACTIONS === true){
33195        WriteToDebug("Configuration specifies interactions should not be reported, exiting.");
33196        return true;
33197    }
33198
33199    ClearErrorInfo();
33200
33201    if (! IsLoaded()){
33202        SetErrorInfo(ERROR_NOT_LOADED, "Cannot make calls to the LMS before calling Start");
33203        return false;
33204    }
33205
33206    strID = new String(strID);
33207
33208    var aryResponse;
33209    var aryCorrectResponse;
33210
33211    //translate the 3 possible argument types into to an array of ResponseIdentifier objects to be passed to the standard functions
33212    if (response === null) {
33213        if (! ALLOW_INTERACTION_NULL_LEARNER_RESPONSE) {
33214            SetErrorInfo(ERROR_INVALID_INTERACTION_RESPONSE, "The response is not in the correct format (null response not allowed)");
33215            return false;
33216        }
33217
33218        aryResponse = null;
33219    }
33220    else if (response.constructor == String){
33221
33222        aryResponse = new Array();
33223        var responseIdentifier = CreateResponseIdentifier(response, response);
33224
33225        if (responseIdentifier == false){
33226            SetErrorInfo(ERROR_INVALID_INTERACTION_RESPONSE, "The response is not in the correct format");
33227            return false;
33228        }
33229
33230        aryResponse[0] = responseIdentifier;
33231    }
33232    else if (response.constructor == ResponseIdentifier){
33233        aryResponse = new Array();
33234        aryResponse[0] = response;
33235    }
33236    else if (response.constructor == Array || response.constructor.toString().search("Array") > 0){     //sometimes the array constructor isn't handled correctly so we check its string
33237        aryResponse = response;
33238    }
33239    else if(window.console && response.constructor.toString() == '(Internal Function)' && response.length > 0){
33240        // we're in Safari. Safari doesn't return the Array function as a string like IE and FireFox. It does however return the ResponseIdentifier function as a string
33241        aryResponse = response;
33242    }
33243    else{
33244        if(window.console){
33245            window.console.log("ERROR_INVALID_INTERACTION_RESPONSE :: The response is not in the correct format.");
33246        }
33247        SetErrorInfo(ERROR_INVALID_INTERACTION_RESPONSE, "The response is not in the correct format");
33248        return false;
33249    }
33250
33251
33252    if (correctResponse != null && correctResponse != undefined && correctResponse != ""){
33253        if (correctResponse.constructor == String){
33254
33255            aryCorrectResponse = new Array();
33256            responseIdentifier = CreateResponseIdentifier(correctResponse, correctResponse);
33257
33258            if (responseIdentifier == false){
33259                SetErrorInfo(ERROR_INVALID_INTERACTION_RESPONSE, "The correct response is not in the correct format");
33260                return false;
33261            }
33262
33263            aryCorrectResponse[0] = responseIdentifier;
33264
33265        }
33266        else if (correctResponse.constructor == ResponseIdentifier){
33267            aryCorrectResponse = new Array();
33268            aryCorrectResponse[0] = correctResponse;
33269        }
33270        else if (correctResponse.constructor == Array || correctResponse.constructor.toString().search("Array") > 0){       //sometimes the array constructor isn't handled correctly so we check its string
33271            aryCorrectResponse = correctResponse;
33272        }
33273        else if(window.console && correctResponse.constructor.toString() == '(Internal Function)' && correctResponse.length > 0){
33274        // we're in Safari. Safari doesn't return the Array function as a string like IE and FireFox. It does however return the ResponseIdentifier function as a string
33275            aryCorrectResponse = correctResponse;
33276        }
33277        else{
33278            SetErrorInfo(ERROR_INVALID_INTERACTION_RESPONSE, "The correct response is not in the correct format");
33279            return false;
33280        }
33281    }
33282    else{
33283        aryCorrectResponse = new Array();
33284    }
33285
33286    var dtmTime = new Date();
33287
33288    WriteToDebug("Calling to LMS");
33289    return objLMS.RecordMultipleChoiceInteraction(strID, aryResponse, blnCorrect, aryCorrectResponse, strDescription, intWeighting, intLatency, strLearningObjectiveID, dtmTime);
33290
33291
33292}
33293
33294
33295function RecordFillInInteraction(strID, strResponse, blnCorrect, strCorrectResponse, strDescription, intWeighting, intLatency, strLearningObjectiveID){
33296
33297    //clean the ID if needed
33298    strID = CreateValidIdentifier(strID);
33299    strLearningObjectiveID = CreateValidIdentifier(strLearningObjectiveID);
33300
33301    WriteToDebug("In RecordFillInInteraction strID=" + strID + ", strResponse=" + strResponse +
33302                    ", blnCorrect=" + blnCorrect + ", strCorrectResponse=" + strCorrectResponse +
33303                    ", strDescription=" + strDescription + ", intWeighting=" + intWeighting + ", intLatency=" + intLatency + ", strLearningObjectiveID=" + strLearningObjectiveID);
33304
33305    if (!(typeof(DO_NOT_REPORT_INTERACTIONS) == "undefined") && DO_NOT_REPORT_INTERACTIONS === true){
33306        WriteToDebug("Configuration specifies interactions should not be reported, exiting.");
33307        return true;
33308    }
33309
33310    ClearErrorInfo();
33311
33312    if (! IsLoaded()){
33313        SetErrorInfo(ERROR_NOT_LOADED, "Cannot make calls to the LMS before calling Start");
33314        return false;
33315    }
33316
33317    var dtmTime = new Date();
33318
33319    WriteToDebug("Calling to LMS");
33320    return objLMS.RecordFillInInteraction(strID, strResponse, blnCorrect, strCorrectResponse, strDescription, intWeighting, intLatency, strLearningObjectiveID, dtmTime);
33321
33322}
33323
33324
33325//public
33326//strResponse and strCorrectResponse can be either:
33327//  -a single MatchingResponse object
33328//  -an array of MatchingResponse objects representing multiple selections that must be/were made
33329
33330function RecordMatchingInteraction(strID, response, blnCorrect, correctResponse, strDescription, intWeighting, intLatency, strLearningObjectiveID){
33331
33332    //clean the ID if needed
33333    strID = CreateValidIdentifier(strID);
33334    strLearningObjectiveID = CreateValidIdentifier(strLearningObjectiveID);
33335
33336    WriteToDebug("In RecordMatchingInteraction strID=" + strID + ", response=" + response +
33337                    ", blnCorrect=" + blnCorrect + ", correctResponse=" + correctResponse +
33338                    ", strDescription=" + strDescription + ", intWeighting=" + intWeighting + ", intLatency=" + intLatency + ", strLearningObjectiveID=" + strLearningObjectiveID);
33339
33340    if (!(typeof(DO_NOT_REPORT_INTERACTIONS) == "undefined") && DO_NOT_REPORT_INTERACTIONS === true){
33341        WriteToDebug("Configuration specifies interactions should not be reported, exiting.");
33342        return true;
33343    }
33344
33345    ClearErrorInfo();
33346
33347    if (! IsLoaded()){
33348        SetErrorInfo(ERROR_NOT_LOADED, "Cannot make calls to the LMS before calling Start");
33349        return false;
33350    }
33351
33352    var aryResponse;
33353    var aryCorrectResponse;
33354
33355    if (response === null) {
33356        if (! ALLOW_INTERACTION_NULL_LEARNER_RESPONSE) {
33357            SetErrorInfo(ERROR_INVALID_INTERACTION_RESPONSE, "The response is not in the correct format (null response not allowed)");
33358            return false;
33359        }
33360
33361        aryResponse = null;
33362    }
33363    else if (response.constructor == MatchingResponse){
33364        aryResponse = new Array();
33365        aryResponse[0] = response;
33366    }
33367    else if (response.constructor == Array || response.constructor.toString().search("Array") > 0){     //sometimes the array constructor isn't handled correctly so we check its string
33368        aryResponse = response;
33369    }
33370    else if(window.console && response.constructor.toString() == '(Internal Function)' && response.length > 0){
33371        // we're in Safari. Safari doesn't return the Array function as a string like IE and FireFox. It does however return the ResponseIdentifier function as a string
33372        aryResponse = response;
33373    }
33374    else{
33375        SetErrorInfo(ERROR_INVALID_INTERACTION_RESPONSE, "The response is not in the correct format");
33376        return false;
33377    }
33378
33379    if (correctResponse != null && correctResponse != undefined){
33380        if (correctResponse.constructor == MatchingResponse){
33381            aryCorrectResponse = new Array();
33382            aryCorrectResponse[0] = correctResponse;
33383        }
33384        else if (correctResponse.constructor == Array || correctResponse.constructor.toString().search("Array") > 0){       //sometimes the array constructor isn't handled correctly so we check its string
33385            aryCorrectResponse = correctResponse;
33386        }
33387        else if(window.console && correctResponse.constructor.toString() == '(Internal Function)' && correctResponse.length > 0){
33388        // we're in Safari. Safari doesn't return the Array function as a string like IE and FireFox. It does however return the ResponseIdentifier function as a string
33389            aryCorrectResponse = correctResponse;
33390        }
33391        else{
33392            SetErrorInfo(ERROR_INVALID_INTERACTION_RESPONSE, "The response is not in the correct format");
33393            return false;
33394        }
33395    }
33396    else{
33397        aryCorrectResponse = new Array();
33398    }
33399
33400    var dtmTime = new Date();
33401
33402    WriteToDebug("Calling to LMS");
33403    return objLMS.RecordMatchingInteraction(strID, aryResponse, blnCorrect, aryCorrectResponse, strDescription, intWeighting, intLatency, strLearningObjectiveID, dtmTime);
33404
33405}
33406
33407function RecordPerformanceInteraction(strID, strResponse, blnCorrect, strCorrectResponse, strDescription, intWeighting, intLatency, strLearningObjectiveID){
33408
33409    //clean the ID if needed
33410    strID = CreateValidIdentifier(strID);
33411    strLearningObjectiveID = CreateValidIdentifier(strLearningObjectiveID);
33412
33413    WriteToDebug("In RecordPerformanceInteraction strID=" + strID + ", strResponse=" + strResponse +
33414                    ", blnCorrect=" + blnCorrect + ", strCorrectResponse=" + strCorrectResponse +
33415                    ", strDescription=" + strDescription + ", intWeighting=" + intWeighting + ", intLatency=" + intLatency + ", strLearningObjectiveID=" + strLearningObjectiveID);
33416
33417    if (!(typeof(DO_NOT_REPORT_INTERACTIONS) == "undefined") && DO_NOT_REPORT_INTERACTIONS === true){
33418        WriteToDebug("Configuration specifies interactions should not be reported, exiting.");
33419        return true;
33420    }
33421
33422    ClearErrorInfo();
33423
33424    if (! IsLoaded()){
33425        SetErrorInfo(ERROR_NOT_LOADED, "Cannot make calls to the LMS before calling Start");
33426        return false;
33427    }
33428
33429    var dtmTime = new Date();
33430
33431    WriteToDebug("Calling to LMS");
33432    return objLMS.RecordPerformanceInteraction(strID, strResponse, blnCorrect, strCorrectResponse, strDescription, intWeighting, intLatency, strLearningObjectiveID, dtmTime);
33433}
33434
33435//public
33436//response and correctResponse can be either:
33437//  -a string representing the response
33438//  -a single ResponseIdentifier object
33439//  -an array of ResponseIdentifier objects representing multiple steps that must be/were made
33440
33441function RecordSequencingInteraction(strID, response, blnCorrect, correctResponse, strDescription, intWeighting, intLatency, strLearningObjectiveID){
33442
33443    //clean the ID if needed
33444    strID = CreateValidIdentifier(strID);
33445    strLearningObjectiveID = CreateValidIdentifier(strLearningObjectiveID);
33446
33447    WriteToDebug("In RecordSequencingInteraction strID=" + strID + ", response=" + response +
33448                    ", blnCorrect=" + blnCorrect + ", correctResponse=" + correctResponse +
33449                    ", strDescription=" + strDescription + ", intWeighting=" + intWeighting + ", intLatency=" + intLatency + ", strLearningObjectiveID=" + strLearningObjectiveID);
33450
33451    if (!(typeof(DO_NOT_REPORT_INTERACTIONS) == "undefined") && DO_NOT_REPORT_INTERACTIONS === true){
33452        WriteToDebug("Configuration specifies interactions should not be reported, exiting.");
33453        return true;
33454    }
33455
33456    ClearErrorInfo();
33457
33458    if (! IsLoaded()){
33459        SetErrorInfo(ERROR_NOT_LOADED, "Cannot make calls to the LMS before calling Start");
33460        return false;
33461    }
33462
33463
33464    var aryResponse;
33465    var aryCorrectResponse;
33466
33467    //translate the 3 possible argument types into to an array of ResponseIdentifier objects to be passed to the standard functions
33468    if (response === null) {
33469        if (! ALLOW_INTERACTION_NULL_LEARNER_RESPONSE) {
33470            SetErrorInfo(ERROR_INVALID_INTERACTION_RESPONSE, "The response is not in the correct format (null response not allowed)");
33471            return false;
33472        }
33473
33474        aryResponse = null;
33475    }
33476    else if (response.constructor == String){
33477
33478        aryResponse = new Array();
33479        var responseIdentifier = CreateResponseIdentifier(response, response);
33480
33481        if (responseIdentifier == false){
33482            SetErrorInfo(ERROR_INVALID_INTERACTION_RESPONSE, "The response is not in the correct format");
33483            return false;
33484        }
33485
33486        aryResponse[0] = responseIdentifier;
33487    }
33488    else if (response.constructor == ResponseIdentifier){
33489        aryResponse = new Array();
33490        aryResponse[0] = response;
33491    }
33492    else if (response.constructor == Array || response.constructor.toString().search("Array") > 0){     //sometimes the array constructor isn't handled correctly so we check its string
33493        aryResponse = response;
33494    }
33495    else if(window.console && response.constructor.toString() == '(Internal Function)' && response.length > 0){
33496        // we're in Safari. Safari doesn't return the Array function as a string like IE and FireFox. It does however return the ResponseIdentifier function as a string
33497        aryResponse = response;
33498    }
33499    else{
33500        SetErrorInfo(ERROR_INVALID_INTERACTION_RESPONSE, "The response is not in the correct format");
33501        return false;
33502    }
33503
33504
33505    if (correctResponse != null && correctResponse != undefined && correctResponse != ""){
33506        if (correctResponse.constructor == String){
33507
33508            aryCorrectResponse = new Array();
33509            responseIdentifier = CreateResponseIdentifier(correctResponse, correctResponse);
33510
33511            if (responseIdentifier == false){
33512                SetErrorInfo(ERROR_INVALID_INTERACTION_RESPONSE, "The correct response is not in the correct format");
33513                return false;
33514            }
33515
33516            aryCorrectResponse[0] = responseIdentifier;
33517
33518        }
33519        else if (correctResponse.constructor == ResponseIdentifier){
33520            aryCorrectResponse = new Array();
33521            aryCorrectResponse[0] = correctResponse;
33522        }
33523        else if (correctResponse.constructor == Array || correctResponse.constructor.toString().search("Array") > 0){       //sometimes the array constructor isn't handled correctly so we check its string
33524            aryCorrectResponse = correctResponse;
33525        }
33526        else if(window.console && correctResponse.constructor.toString() == '(Internal Function)' && correctResponse.length > 0){
33527        // we're in Safari. Safari doesn't return the Array function as a string like IE and FireFox. It does however return the ResponseIdentifier function as a string
33528            aryCorrectResponse = correctResponse;
33529        }
33530        else{
33531            SetErrorInfo(ERROR_INVALID_INTERACTION_RESPONSE, "The correct response is not in the correct format");
33532            return false;
33533        }
33534    }
33535    else{
33536        aryCorrectResponse = new Array();
33537    }
33538
33539
33540    var dtmTime = new Date();
33541
33542    WriteToDebug("Calling to LMS");
33543    return objLMS.RecordSequencingInteraction(strID, aryResponse, blnCorrect, aryCorrectResponse, strDescription, intWeighting, intLatency, strLearningObjectiveID, dtmTime);
33544
33545}
33546
33547//public
33548//response and correctResponse can be either:
33549//  -a string representing the response
33550//  -a single ResponseIdentifier object
33551
33552function RecordLikertInteraction(strID, response, blnCorrect, correctResponse, strDescription, intWeighting, intLatency, strLearningObjectiveID){
33553
33554    //clean the ID if needed
33555    strID = CreateValidIdentifier(strID);
33556    strLearningObjectiveID = CreateValidIdentifier(strLearningObjectiveID);
33557
33558    WriteToDebug("In RecordLikertInteraction strID=" + strID + ", response=" + response +
33559                    ", blnCorrect=" + blnCorrect + ", correctResponse=" + correctResponse +
33560                    ", strDescription=" + strDescription + ", intWeighting=" + intWeighting + ", intLatency=" + intLatency + ", strLearningObjectiveID=" + strLearningObjectiveID);
33561
33562    if (!(typeof(DO_NOT_REPORT_INTERACTIONS) == "undefined") && DO_NOT_REPORT_INTERACTIONS === true){
33563        WriteToDebug("Configuration specifies interactions should not be reported, exiting.");
33564        return true;
33565    }
33566
33567    ClearErrorInfo();
33568
33569    if (! IsLoaded()){
33570        SetErrorInfo(ERROR_NOT_LOADED, "Cannot make calls to the LMS before calling Start");
33571        return false;
33572    }
33573
33574    var riResponse;
33575    var riCorrectResponse;
33576
33577    //translate the 3 possible argument types into to an array of ResponseIdentifier objects to be passed to the standard functions
33578    if (response === null) {
33579        if (! ALLOW_INTERACTION_NULL_LEARNER_RESPONSE) {
33580            SetErrorInfo(ERROR_INVALID_INTERACTION_RESPONSE, "The response is not in the correct format (null response not allowed)");
33581            return false;
33582        }
33583
33584        riResponse = null;
33585    }
33586    else if (response.constructor == String){
33587        riResponse = CreateResponseIdentifier(response, response);
33588    }
33589    else if (response.constructor == ResponseIdentifier){
33590        riResponse = response;
33591    }
33592    else{
33593        SetErrorInfo(ERROR_INVALID_INTERACTION_RESPONSE, "The response is not in the correct format");
33594        return false;
33595    }
33596
33597
33598    if (correctResponse == null || correctResponse == undefined){
33599        riCorrectResponse = null;
33600    }
33601    else if (correctResponse.constructor == ResponseIdentifier){
33602        riCorrectResponse = correctResponse;
33603    }
33604    else if (correctResponse.constructor == String){
33605        riCorrectResponse = CreateResponseIdentifier(correctResponse, correctResponse);
33606    }
33607    else{
33608        SetErrorInfo(ERROR_INVALID_INTERACTION_RESPONSE, "The response is not in the correct format");
33609        return false;
33610    }
33611
33612    var dtmTime = new Date();
33613
33614    WriteToDebug("Calling to LMS");
33615    return objLMS.RecordLikertInteraction(strID, riResponse, blnCorrect, riCorrectResponse, strDescription, intWeighting, intLatency, strLearningObjectiveID, dtmTime);
33616
33617}
33618
33619function RecordNumericInteraction(strID, strResponse, blnCorrect, strCorrectResponse, strDescription, intWeighting, intLatency, strLearningObjectiveID){
33620
33621    //clean the IDs if needed
33622    strID = CreateValidIdentifier(strID);
33623    strLearningObjectiveID = CreateValidIdentifier(strLearningObjectiveID);
33624
33625    WriteToDebug("In RecordNumericInteraction strID=" + strID + ", strResponse=" + strResponse +
33626                    ", blnCorrect=" + blnCorrect + ", strCorrectResponse=" + strCorrectResponse +
33627                    ", strDescription=" + strDescription + ", intWeighting=" + intWeighting + ", intLatency=" + intLatency + ", strLearningObjectiveID=" + strLearningObjectiveID);
33628
33629    if (!(typeof(DO_NOT_REPORT_INTERACTIONS) == "undefined") && DO_NOT_REPORT_INTERACTIONS === true){
33630        WriteToDebug("Configuration specifies interactions should not be reported, exiting.");
33631        return true;
33632    }
33633
33634    ClearErrorInfo();
33635
33636    if (! IsLoaded()){
33637        SetErrorInfo(ERROR_NOT_LOADED, "Cannot make calls to the LMS before calling Start");
33638        return false;
33639    }
33640
33641    if ((strResponse === null && ! ALLOW_INTERACTION_NULL_LEARNER_RESPONSE) || (strResponse !== null && ! IsValidDecimal(strResponse))){
33642        WriteToDebug("ERROR - Invalid Response, not a valid decmial");
33643        SetErrorInfo(ERROR_INVALID_NUMBER, "Invalid Response passed to RecordNumericInteraction (not a valid decimal), strResponse=" + strResponse);
33644        return false;
33645    }
33646
33647    var dtmTime = new Date();
33648
33649    WriteToDebug("Calling to LMS");
33650    return objLMS.RecordNumericInteraction(strID, strResponse, blnCorrect, strCorrectResponse, strDescription, intWeighting, intLatency, strLearningObjectiveID, dtmTime);
33651}
33652
33653
33654//State Functions
33655
33656//public
33657function GetStatus(){
33658
33659    WriteToDebug("In GetStatus");
33660
33661    ClearErrorInfo();
33662
33663    if (! IsLoaded()){
33664        SetErrorInfo(ERROR_NOT_LOADED, "Cannot make calls to the LMS before calling Start");
33665        return LESSON_STATUS_INCOMPLETE;
33666    }
33667
33668    return objLMS.GetStatus();
33669
33670}
33671
33672//public
33673function ResetStatus(){
33674    WriteToDebug("In ResetStatus");
33675
33676    ClearErrorInfo();
33677
33678    if (! IsLoaded()){
33679        SetErrorInfo(ERROR_NOT_LOADED, "Cannot make calls to the LMS before calling Start");
33680        return false;
33681    }
33682
33683    WriteToDebug("Setting blnStatusWasSet to false");
33684
33685    blnStatusWasSet = false;
33686
33687    return objLMS.ResetStatus();
33688}
33689
33690//public
33691function GetProgressMeasure(){
33692    WriteToDebug("In GetProgressMeasure");
33693
33694    ClearErrorInfo();
33695
33696    if (! IsLoaded()){
33697        SetErrorInfo(ERROR_NOT_LOADED, "Cannot make calls to the LMS before calling Start");
33698        return LESSON_STATUS_INCOMPLETE;
33699    }
33700
33701    return objLMS.GetProgressMeasure();
33702}
33703//public
33704function SetProgressMeasure(numMeasure){
33705    WriteToDebug("In SetProgressMeasure, passing in: "+ numMeasure);
33706
33707    ClearErrorInfo();
33708
33709    if (! IsLoaded()){
33710        SetErrorInfo(ERROR_NOT_LOADED, "Cannot make calls to the LMS before calling Start");
33711        return LESSON_STATUS_INCOMPLETE;
33712    }
33713
33714    return objLMS.SetProgressMeasure(numMeasure);
33715}
33716
33717//public
33718//optional to call
33719function SetPassed(){
33720    WriteToDebug("In SetPassed");
33721
33722    ClearErrorInfo();
33723
33724    if (! IsLoaded()){
33725        SetErrorInfo(ERROR_NOT_LOADED, "Cannot make calls to the LMS before calling Start");
33726        return false;
33727    }
33728
33729    WriteToDebug("Setting blnStatusWasSet to true");
33730
33731    blnStatusWasSet = true;
33732
33733    return objLMS.SetPassed();
33734
33735}
33736
33737//public
33738function SetFailed(){
33739    WriteToDebug("In SetFailed");
33740
33741    ClearErrorInfo();
33742
33743    if (! IsLoaded()){
33744        SetErrorInfo(ERROR_NOT_LOADED, "Cannot make calls to the LMS before calling Start");
33745        return false;
33746    }
33747
33748    WriteToDebug("Setting blnStatusWasSet to true");
33749
33750    blnStatusWasSet = true;
33751
33752    return objLMS.SetFailed();
33753
33754}
33755
33756
33757//public
33758function GetEntryMode(){
33759    WriteToDebug("In GetEntryMode");
33760    ClearErrorInfo();
33761
33762    if (! IsLoaded()){
33763        SetErrorInfo(ERROR_NOT_LOADED, "Cannot make calls to the LMS before calling Start");
33764        return ENTRY_FIRST_TIME;
33765    }
33766
33767    return objLMS.GetEntryMode();
33768
33769}
33770
33771//public
33772function GetLessonMode(){
33773    WriteToDebug("In GetLessonMode");
33774    ClearErrorInfo();
33775
33776    if (! IsLoaded()){
33777        SetErrorInfo(ERROR_NOT_LOADED, "Cannot make calls to the LMS before calling Start");
33778        return MODE_NORMAL;
33779    }
33780
33781    return objLMS.GetLessonMode();
33782
33783}
33784
33785//public
33786function GetTakingForCredit(){
33787    WriteToDebug("In GetTakingForCredit");
33788    ClearErrorInfo();
33789
33790    if (! IsLoaded()){
33791        SetErrorInfo(ERROR_NOT_LOADED, "Cannot make calls to the LMS before calling Start");
33792        return false;
33793    }
33794
33795    return objLMS.GetTakingForCredit();
33796
33797}
33798
33799
33800//Objectives
33801
33802//public
33803function SetObjectiveScore(strObjectiveID, intScore, intMaxScore, intMinScore){
33804
33805    WriteToDebug("In SetObjectiveScore, intObjectiveID=" + strObjectiveID + ", intScore=" + intScore + ", intMaxScore=" + intMaxScore + ", intMinScore=" + intMinScore);
33806
33807    ClearErrorInfo();
33808
33809    if (! IsLoaded()){
33810        SetErrorInfo(ERROR_NOT_LOADED, "Cannot make calls to the LMS before calling Start");
33811        return false;
33812    }
33813
33814    strObjectiveID = new String(strObjectiveID);
33815    if (strObjectiveID.replace(" ", "") == ""){
33816        WriteToDebug("ERROR - Invalid ObjectiveID, empty string");
33817        SetErrorInfo(ERROR_INVALID_ID, "Invalid ObjectiveID passed to SetObjectiveScore (must have a value), strObjectiveID=" + strObjectiveID);
33818        return false;
33819    }
33820
33821    if (! IsValidDecimal(intScore)){
33822        WriteToDebug("ERROR - Invalid Score, not a valid decmial");
33823        SetErrorInfo(ERROR_INVALID_NUMBER, "Invalid Score passed to SetObjectiveScore (not a valid decimal), intScore=" + intScore);
33824        return false;
33825    }
33826
33827    if (! IsValidDecimal(intMaxScore)){
33828        WriteToDebug("ERROR - Invalid Max Score, not a valid decmial");
33829        SetErrorInfo(ERROR_INVALID_NUMBER, "Invalid Max Score passed to SetObjectiveScore (not a valid decimal), intMaxScore=" + intMaxScore);
33830        return false;
33831    }
33832
33833    if (! IsValidDecimal(intMinScore)){
33834        WriteToDebug("ERROR - Invalid Min Score, not a valid decmial");
33835        SetErrorInfo(ERROR_INVALID_NUMBER, "Invalid Min Score passed to SetObjectiveScore (not a valid decimal), intMinScore=" + intMinScore);
33836        return false;
33837    }
33838
33839    WriteToDebug("Converting Scores to floats");
33840    intScore = parseFloat(intScore);
33841    intMaxScore = parseFloat(intMaxScore);
33842    intMinScore = parseFloat(intMinScore);
33843
33844    if (intScore < 0 || intScore > 100){
33845        WriteToDebug("ERROR - Invalid Score, out of range");
33846        SetErrorInfo(ERROR_INVALID_NUMBER, "Invalid Score passed to SetObjectiveScore (must be between 0-100), intScore=" + intScore);
33847        return false;
33848    }
33849
33850    if (intMaxScore < 0 || intMaxScore > 100){
33851        WriteToDebug("ERROR - Invalid Max Score, out of range");
33852        SetErrorInfo(ERROR_INVALID_NUMBER, "Invalid Max Score passed to SetObjectiveScore (must be between 0-100), intMaxScore=" + intMaxScore);
33853        return false;
33854    }
33855
33856    if (intMinScore < 0 || intMinScore > 100){
33857        WriteToDebug("ERROR - Invalid Min Score, out of range");
33858        SetErrorInfo(ERROR_INVALID_NUMBER, "Invalid Min Score passed to SetObjectiveScore (must be between 0-100), intMinScore=" + intMinScore);
33859        return false;
33860    }
33861
33862    WriteToDebug("Calling To LMS");
33863    return objLMS.SetObjectiveScore(strObjectiveID, intScore, intMaxScore, intMinScore);
33864
33865}
33866
33867//public
33868function SetObjectiveStatus(strObjectiveID, Lesson_Status){
33869
33870    WriteToDebug("In SetObjectiveStatus strObjectiveID=" + strObjectiveID + ", Lesson_Status=" + Lesson_Status);
33871
33872    ClearErrorInfo();
33873
33874
33875    if (! IsLoaded()){
33876        SetErrorInfo(ERROR_NOT_LOADED, "Cannot make calls to the LMS before calling Start");
33877        return false;
33878    }
33879
33880
33881    strObjectiveID = new String(strObjectiveID);
33882    if (strObjectiveID.replace(" ", "") == ""){
33883        WriteToDebug("ERROR - Invalid ObjectiveID, empty string");
33884        SetErrorInfo(ERROR_INVALID_ID, "Invalid ObjectiveID passed to SetObjectiveStatus (must have a value), strObjectiveID=" + strObjectiveID);
33885        return false;
33886    }
33887
33888    if (
33889       (Lesson_Status != LESSON_STATUS_PASSED) &&
33890       (Lesson_Status != LESSON_STATUS_COMPLETED) &&
33891       (Lesson_Status != LESSON_STATUS_FAILED) &&
33892       (Lesson_Status != LESSON_STATUS_INCOMPLETE) &&
33893       (Lesson_Status != LESSON_STATUS_BROWSED) &&
33894       (Lesson_Status != LESSON_STATUS_NOT_ATTEMPTED)
33895       ){
33896        WriteToDebug("ERROR - Invalid Status");
33897        SetErrorInfo(ERROR_INVALID_STATUS, "Invalid status passed to SetObjectiveStatus, Lesson_Status=" + Lesson_Status);
33898        return false;
33899
33900    }
33901
33902    WriteToDebug("Calling To LMS");
33903    return objLMS.SetObjectiveStatus(strObjectiveID, Lesson_Status);
33904
33905
33906}
33907
33908
33909//public
33910function GetObjectiveStatus(strObjectiveID){
33911
33912    WriteToDebug("In GetObjectiveStatus, strObjectiveID=" + strObjectiveID);
33913
33914    ClearErrorInfo();
33915
33916    if (! IsLoaded()){
33917        SetErrorInfo(ERROR_NOT_LOADED, "Cannot make calls to the LMS before calling Start");
33918        return false;
33919    }
33920
33921    return objLMS.GetObjectiveStatus(strObjectiveID);
33922}
33923
33924
33925
33926
33927//public
33928function SetObjectiveDescription(strObjectiveID, strObjectiveDescription){
33929
33930    WriteToDebug("In SetObjectiveDescription strObjectiveID=" + strObjectiveID + ", strObjectiveDescription=" + strObjectiveDescription);
33931
33932    ClearErrorInfo();
33933
33934
33935    if (! IsLoaded()){
33936        SetErrorInfo(ERROR_NOT_LOADED, "Cannot make calls to the LMS before calling Start");
33937        return false;
33938    }
33939
33940
33941    strObjectiveID = new String(strObjectiveID);
33942    if (strObjectiveID.replace(" ", "") == ""){
33943        WriteToDebug("ERROR - Invalid ObjectiveID, empty string");
33944        SetErrorInfo(ERROR_INVALID_ID, "Invalid ObjectiveID passed to SetObjectiveStatus (must have a value), strObjectiveID=" + strObjectiveID);
33945        return false;
33946    }
33947
33948
33949    //include validation for string length?
33950
33951    WriteToDebug("Calling To LMS");
33952    return objLMS.SetObjectiveDescription(strObjectiveID, strObjectiveDescription);
33953
33954
33955}
33956
33957
33958//public
33959function GetObjectiveDescription(strObjectiveID){
33960
33961    WriteToDebug("In GetObjectiveDescription, strObjectiveID=" + strObjectiveID);
33962
33963    ClearErrorInfo();
33964
33965    if (! IsLoaded()){
33966        SetErrorInfo(ERROR_NOT_LOADED, "Cannot make calls to the LMS before calling Start");
33967        return false;
33968    }
33969
33970    return objLMS.GetObjectiveDescription(strObjectiveID);
33971}
33972
33973
33974
33975
33976
33977//public
33978function GetObjectiveScore(strObjectiveID){
33979
33980    WriteToDebug("In GetObjectiveScore, strObjectiveID=" + strObjectiveID);
33981
33982    ClearErrorInfo();
33983
33984    if (! IsLoaded()){
33985        SetErrorInfo(ERROR_NOT_LOADED, "Cannot make calls to the LMS before calling Start");
33986        return false;
33987    }
33988
33989    return objLMS.GetObjectiveScore(strObjectiveID);
33990}
33991
33992
33993//public
33994function IsLmsPresent(){
33995    return blnLmsPresent;
33996}
33997
33998//public
33999function SetObjectiveProgressMeasure(strObjectiveID, strObjectiveProgressMeasure){
34000
34001    WriteToDebug("In SetObjectiveProgressMeasure strObjectiveID=" + strObjectiveID + ", strObjectiveProgressMeasure=" + strObjectiveProgressMeasure);
34002
34003    ClearErrorInfo();
34004
34005
34006    if (! IsLoaded()){
34007        SetErrorInfo(ERROR_NOT_LOADED, "Cannot make calls to the LMS before calling Start");
34008        return false;
34009    }
34010
34011
34012    strObjectiveID = new String(strObjectiveID);
34013    if (strObjectiveID.replace(" ", "") == ""){
34014        WriteToDebug("ERROR - Invalid ObjectiveID, empty string");
34015        SetErrorInfo(ERROR_INVALID_ID, "Invalid ObjectiveID passed to SetObjectiveProgressMeasure (must have a value), strObjectiveID=" + strObjectiveID);
34016        return false;
34017    }
34018
34019
34020    //include validation for string length?
34021
34022    WriteToDebug("Calling To LMS");
34023    return objLMS.SetObjectiveProgressMeasure(strObjectiveID, strObjectiveProgressMeasure);
34024
34025
34026}
34027
34028
34029//public
34030function GetObjectiveProgressMeasure(strObjectiveID){
34031
34032    WriteToDebug("In GetObjectiveProgressMeasure, strObjectiveID=" + strObjectiveID);
34033
34034    ClearErrorInfo();
34035
34036    if (! IsLoaded()){
34037        SetErrorInfo(ERROR_NOT_LOADED, "Cannot make calls to the LMS before calling Start");
34038        return false;
34039    }
34040
34041    return objLMS.GetObjectiveProgressMeasure(strObjectiveID);
34042}
34043
34044
34045//public
34046function SetNavigationRequest(strNavRequest){
34047    WriteToDebug("In SetNavigationRequest");
34048
34049    ClearErrorInfo();
34050
34051    if (! IsLoaded()){
34052        SetErrorInfo(ERROR_NOT_LOADED, "Cannot make calls to the LMS before calling Start");
34053        return false;
34054    }
34055
34056    return objLMS.SetNavigationRequest(strNavRequest);
34057}
34058//public
34059function GetNavigationRequest(){
34060    WriteToDebug("In GetNavigationRequest");
34061
34062    ClearErrorInfo();
34063
34064    if (! IsLoaded()){
34065        SetErrorInfo(ERROR_NOT_LOADED, "Cannot make calls to the LMS before calling Start");
34066        return false;
34067    }
34068
34069    return objLMS.GetNavigationRequest();
34070}
34071
34072//NOTE ON INTERACTION RETRIEVAL
34073//A.  It is only available in certain standards, standards where it is unavailable will return nothing
34074//B.  The interaction records are currently reported using "journaling", whereby each entry is appended
34075//      Retrieval methods will retrieve only the most recent value
34076//public
34077function GetInteractionType(strInteractionID)
34078{
34079    // Clean the ID if needed
34080    strInteractionID = CreateValidIdentifier(strInteractionID);
34081
34082    WriteToDebug("In GetInteractionType, strInteractionID=" + strInteractionID);
34083
34084    ClearErrorInfo();
34085
34086    if (! IsLoaded()){
34087        SetErrorInfo(ERROR_NOT_LOADED, "Cannot make calls to the LMS before calling Start");
34088        return false;
34089    }
34090
34091    return objLMS.GetInteractionType(strInteractionID);
34092}
34093
34094//public
34095function GetInteractionTimestamp(strInteractionID)
34096{
34097    // Clean the ID if needed
34098    strInteractionID = CreateValidIdentifier(strInteractionID);
34099
34100    WriteToDebug("In GetInteractionTimestamp, strInteractionID=" + strInteractionID);
34101
34102    ClearErrorInfo();
34103
34104    if (! IsLoaded()){
34105        SetErrorInfo(ERROR_NOT_LOADED, "Cannot make calls to the LMS before calling Start");
34106        return false;
34107    }
34108
34109    return objLMS.GetInteractionTimestamp(strInteractionID);
34110}
34111
34112
34113
34114//public
34115function GetInteractionCorrectResponses(strInteractionID)
34116{
34117    // Clean the ID if needed
34118    strInteractionID = CreateValidIdentifier(strInteractionID);
34119
34120    WriteToDebug("In GetInteractionCorrectResponses, strInteractionID=" + strInteractionID);
34121
34122    ClearErrorInfo();
34123
34124    if (! IsLoaded()){
34125        SetErrorInfo(ERROR_NOT_LOADED, "Cannot make calls to the LMS before calling Start");
34126        return false;
34127    }
34128
34129    return objLMS.GetInteractionCorrectResponses(strInteractionID);
34130}
34131
34132
34133
34134//public
34135function GetInteractionWeighting(strInteractionID)
34136{
34137    // Clean the ID if needed
34138    strInteractionID = CreateValidIdentifier(strInteractionID);
34139
34140    WriteToDebug("In GetInteractionWeighting, strInteractionID=" + strInteractionID);
34141
34142    ClearErrorInfo();
34143
34144    if (! IsLoaded()){
34145        SetErrorInfo(ERROR_NOT_LOADED, "Cannot make calls to the LMS before calling Start");
34146        return false;
34147    }
34148
34149    return objLMS.GetInteractionWeighting(strInteractionID);
34150}
34151
34152
34153
34154//public
34155function GetInteractionLearnerResponses(strInteractionID)
34156{
34157    // Clean the ID if needed
34158    strInteractionID = CreateValidIdentifier(strInteractionID);
34159
34160    WriteToDebug("In GetInteractionLearnerResponses, strInteractionID=" + strInteractionID);
34161
34162    ClearErrorInfo();
34163
34164    if (! IsLoaded()){
34165        SetErrorInfo(ERROR_NOT_LOADED, "Cannot make calls to the LMS before calling Start");
34166        return false;
34167    }
34168
34169    return objLMS.GetInteractionLearnerResponses(strInteractionID);
34170}
34171
34172
34173
34174//public
34175function GetInteractionResult(strInteractionID)
34176{
34177    // Clean the ID if needed
34178    strInteractionID = CreateValidIdentifier(strInteractionID);
34179
34180    WriteToDebug("In GetInteractionResult, strInteractionID=" + strInteractionID);
34181
34182    ClearErrorInfo();
34183
34184    if (! IsLoaded()){
34185        SetErrorInfo(ERROR_NOT_LOADED, "Cannot make calls to the LMS before calling Start");
34186        return false;
34187    }
34188
34189    return objLMS.GetInteractionResult(strInteractionID);
34190}
34191
34192
34193
34194//public
34195function GetInteractionLatency(strInteractionID)
34196{
34197    // Clean the ID if needed
34198    strInteractionID = CreateValidIdentifier(strInteractionID);
34199
34200    WriteToDebug("In GetInteractionLatency, strInteractionID=" + strInteractionID);
34201
34202    ClearErrorInfo();
34203
34204    if (! IsLoaded()){
34205        SetErrorInfo(ERROR_NOT_LOADED, "Cannot make calls to the LMS before calling Start");
34206        return false;
34207    }
34208
34209    return objLMS.GetInteractionLatency(strInteractionID);
34210}
34211
34212
34213
34214//public
34215function GetInteractionDescription(strInteractionID)
34216{
34217    // Clean the ID if needed
34218    strInteractionID = CreateValidIdentifier(strInteractionID);
34219
34220    WriteToDebug("In GetInteractionDescription, strInteractionID=" + strInteractionID);
34221
34222    ClearErrorInfo();
34223
34224    if (! IsLoaded()){
34225        SetErrorInfo(ERROR_NOT_LOADED, "Cannot make calls to the LMS before calling Start");
34226        return false;
34227    }
34228
34229    return objLMS.GetInteractionDescription(strInteractionID);
34230}
34231
34232
34233//public
34234/*
34235Bucket sizes are listed in characters. In SCORM, all transmitted data is represented as JavaScript strings.
34236JavaScript strings use two bytes for each character so the actual size (in bytes) of what is allocated will be
34237double the value that is passed to this function.
34238*/
34239function CreateDataBucket(strBucketId, intMinSize, intMaxSize){
34240
34241    WriteToDebug("In CreateDataBucket, strBucketId=" + strBucketId + ", intMinSize=" + intMinSize + ", intMaxSize=" + intMaxSize);
34242
34243    ClearErrorInfo();
34244
34245    if (! IsLoaded()){
34246        SetErrorInfo(ERROR_NOT_LOADED, "Cannot make calls to the LMS before calling Start");
34247        return false;
34248    }
34249
34250    strBucketId = new String(strBucketId);
34251    if (strBucketId.replace(" ", "") == ""){
34252        WriteToDebug("ERROR - Invalid BucketId, empty string");
34253        SetErrorInfo(ERROR_INVALID_ID, "Invalid strBucketId passed to CreateDataBucket (must have a value), strBucketId=" + strBucketId);
34254        return false;
34255    }
34256
34257    if ( ! ValidInteger(intMinSize) ){
34258        WriteToDebug("ERROR Invalid Min Size, not an integer");
34259        SetErrorInfo(ERROR_INVALID_NUMBER, "Invalid intMinSize passed to CreateDataBucket (not an integer), intMinSize=" + intMinSize);
34260        return false;
34261    }
34262
34263    if ( ! ValidInteger(intMaxSize) ){
34264        WriteToDebug("ERROR Invalid Max Size, not an integer");
34265        SetErrorInfo(ERROR_INVALID_NUMBER, "Invalid intMaxSize passed to CreateDataBucket (not an integer), intMaxSize=" + intMaxSize);
34266        return false;
34267    }
34268
34269    intMinSize = parseInt(intMinSize, 10);
34270    intMaxSize = parseInt(intMaxSize, 10);
34271
34272    if (intMinSize < 0){
34273        WriteToDebug("ERROR Invalid Min Size, must be greater than or equal to 0");
34274        SetErrorInfo(ERROR_INVALID_NUMBER, "Invalid Min Size passed to CreateDataBucket (must be greater than or equal to 0), intMinSize=" + intMinSize);
34275        return false;
34276    }
34277
34278    if (intMaxSize <= 0){
34279        WriteToDebug("ERROR Invalid Max Size, must be greater than 0");
34280        SetErrorInfo(ERROR_INVALID_NUMBER, "Invalid Max Size passed to CreateDataBucket (must be greater than 0), intMaxSize=" + intMaxSize);
34281        return false;
34282    }
34283
34284    //need to double the values to get from "characters" (the representation exposed by this API) to "octets" the value used by SSP
34285    intMinSize = (intMinSize * 2);
34286    intMaxSize = (intMaxSize * 2);
34287
34288    return objLMS.CreateDataBucket(strBucketId, intMinSize, intMaxSize);
34289}
34290
34291//public
34292function GetDataFromBucket(strBucketId){
34293
34294    WriteToDebug("In GetDataFromBucket, strBucketId=" + strBucketId);
34295
34296    ClearErrorInfo();
34297
34298    if (! IsLoaded()){
34299        SetErrorInfo(ERROR_NOT_LOADED, "Cannot make calls to the LMS before calling Start");
34300        return false;
34301    }
34302
34303    strBucketId = new String(strBucketId);
34304    if (strBucketId.replace(" ", "") == ""){
34305        WriteToDebug("ERROR - Invalid BucketId, empty string");
34306        SetErrorInfo(ERROR_INVALID_ID, "Invalid strBucketId passed to GetDataFromBucket (must have a value), strBucketId=" + strBucketId);
34307        return false;
34308    }
34309
34310    return objLMS.GetDataFromBucket(strBucketId);
34311}
34312
34313//public
34314function PutDataInBucket(strBucketId, strData, blnAppendToEnd){
34315
34316    WriteToDebug("In PutDataInBucket, strBucketId=" + strBucketId + ", blnAppendToEnd=" + blnAppendToEnd + ", strData=" + strData);
34317
34318    ClearErrorInfo();
34319
34320    if (! IsLoaded()){
34321        SetErrorInfo(ERROR_NOT_LOADED, "Cannot make calls to the LMS before calling Start");
34322        return false;
34323    }
34324
34325    strBucketId = new String(strBucketId);
34326    if (strBucketId.replace(" ", "") == ""){
34327        WriteToDebug("ERROR - Invalid BucketId, empty string");
34328        SetErrorInfo(ERROR_INVALID_ID, "Invalid strBucketId passed to PutDataInBucket (must have a value), strBucketId=" + strBucketId);
34329        return false;
34330    }
34331
34332    if (blnAppendToEnd != true){
34333        WriteToDebug("blnAppendToEnd was not explicitly true so setting it to false, blnAppendToEnd=" + blnAppendToEnd);
34334        blnAppendToEnd = false;
34335    }
34336
34337    return objLMS.PutDataInBucket(strBucketId, strData, blnAppendToEnd);
34338}
34339
34340//public
34341function DetectSSPSupport(){
34342    return objLMS.DetectSSPSupport();
34343}
34344
34345//public returns a SSPBucketSize object containing information about the allocated size of the specified bucket
34346function GetBucketInfo(strBucketId){
34347
34348    WriteToDebug("In GetBucketInfo, strBucketId=" + strBucketId );
34349
34350    ClearErrorInfo();
34351
34352    if (! IsLoaded()){
34353        SetErrorInfo(ERROR_NOT_LOADED, "Cannot make calls to the LMS before calling Start");
34354        return false;
34355    }
34356
34357    strBucketId = new String(strBucketId);
34358    if (strBucketId.replace(" ", "") == ""){
34359        WriteToDebug("ERROR - Invalid BucketId, empty string");
34360        SetErrorInfo(ERROR_INVALID_ID, "Invalid strBucketId passed to GetBucketInfo (must have a value), strBucketId=" + strBucketId);
34361        return false;
34362    }
34363
34364    var bucketInfo = objLMS.GetBucketInfo(strBucketId);
34365
34366    //bucket size needs to be halfed to translate from octets to characters
34367    bucketInfo.TotalSpace = (bucketInfo.TotalSpace / 2);
34368    bucketInfo.UsedSpace = (bucketInfo.UsedSpace / 2);
34369
34370    WriteToDebug("GetBucketInfo returning " + bucketInfo );
34371
34372    return bucketInfo;
34373}
34374
34375
34376//Represents the size of an SSP Bucket
34377//Contains two values, a total space and used space, each of which is represented in characters
34378function SSPBucketSize(totalSpace, usedSpace){
34379
34380    this.TotalSpace = totalSpace;
34381    this.UsedSpace = usedSpace;
34382
34383    this.toString = function (){
34384        return "[SSPBucketSize " + this.TotalSpace + ", " + this.UsedSpace + "]";
34385    };
34386}

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.