diff --git a/.gitignore b/.gitignore
index a5d61361a..357b6eac1 100644
--- a/.gitignore
+++ b/.gitignore
@@ -5,3 +5,5 @@
# Node.js
node_modules
/npm-debug.log
+/log/xapi-statements.json
+/public/ITDG
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 67eedef47..9cd00e200 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,16 @@
VIRTUAL WORLD FRAMEWORK CHANGE LOG
==================================
+----------------------------------
+0.8.0
+----------------------------------------------------------------------------------------------------
+Note: (*) indicates an API change.
+
+- CHG*: Handle reflector actions as they arrive, without waiting for a tick.
+- CHG*: Don't record ticks in the queue. Don't tick nodes and model drivers.
+- CHG*: Remove ticking function from model drivers.
+
+
----------------------------------
0.7.0
----------------------------------------------------------------------------------------------------
diff --git a/StartVWFServer.bat b/StartVWFServer.bat
new file mode 100644
index 000000000..5820b7f12
--- /dev/null
+++ b/StartVWFServer.bat
@@ -0,0 +1,7 @@
+@echo off
+set NPM_PATH=%HOMEDRIVE%%HOME_PATH%\AppData\Roaming\npm
+set NODEJS_PATH=%ProgramFiles%\nodejs
+set PATH=%NPM_PATH%;%NODEJS_PATH%;%PATH%
+cd C:\ITDG\VWF
+npm start
+exit
\ No newline at end of file
diff --git a/StartVWFServerMinimized.bat b/StartVWFServerMinimized.bat
new file mode 100644
index 000000000..1bc68eed5
--- /dev/null
+++ b/StartVWFServerMinimized.bat
@@ -0,0 +1,4 @@
+@echo off
+cd C:\ITDG\VWF
+start /min StartVWFServer.bat
+exit
diff --git a/config/custom-environment-variables.json b/config/custom-environment-variables.json
new file mode 100644
index 000000000..52bcebe0e
--- /dev/null
+++ b/config/custom-environment-variables.json
@@ -0,0 +1,13 @@
+{
+ "session": {
+ "secret": "VWF_SESSION_SECRET"
+ },
+ "tdg": {
+ "password": "VWF_TDG_PASSWORD"
+ },
+ "txs": {
+ "amqp_host": "VWF_TXS_AMQP_HOST",
+ "amqp_exchange": "VWF_TXS_AMQP_EXCHANGE",
+ "amqp_channel": "VWF_TXS_AMQP_CHANNEL"
+ }
+}
diff --git a/config/default.json b/config/default.json
new file mode 100644
index 000000000..91ba62307
--- /dev/null
+++ b/config/default.json
@@ -0,0 +1,24 @@
+{
+
+ // Secret for signed sessions. Generate a new secret with
+ // `node -e 'console.log( require( "crypto" ).randomBytes( 32 ).toString( "hex" ) )'`
+
+ "session": {
+ "secret": "0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF"
+ },
+
+ // TDG instructor password.
+
+ "tdg": {
+ "password": "password"
+ },
+
+ // AMQP parameters for the `/sim` route to publish to TXS.
+
+ "txs": {
+ "amqp_host": "localhost",
+ "amqp_exchange": "x_txs",
+ "amqp_channel": "itdg.loadMission"
+ }
+
+}
diff --git a/documents/ITDG/maps/test-R400.jpg b/documents/ITDG/maps/test-R400.jpg
new file mode 100644
index 000000000..72c7c13f5
Binary files /dev/null and b/documents/ITDG/maps/test-R400.jpg differ
diff --git a/documents/ITDG/maps/test-R400.tif b/documents/ITDG/maps/test-R400.tif
new file mode 100644
index 000000000..9531fad19
Binary files /dev/null and b/documents/ITDG/maps/test-R400.tif differ
diff --git a/documents/ITDG/maps/test-R400.tms/16/11653/22630.png b/documents/ITDG/maps/test-R400.tms/16/11653/22630.png
new file mode 100644
index 000000000..d46318b91
Binary files /dev/null and b/documents/ITDG/maps/test-R400.tms/16/11653/22630.png differ
diff --git a/documents/ITDG/maps/test-R400.tms/16/11653/22631.png b/documents/ITDG/maps/test-R400.tms/16/11653/22631.png
new file mode 100644
index 000000000..d0f938922
Binary files /dev/null and b/documents/ITDG/maps/test-R400.tms/16/11653/22631.png differ
diff --git a/documents/ITDG/maps/test-R400.tms/17/23306/45262.png b/documents/ITDG/maps/test-R400.tms/17/23306/45262.png
new file mode 100644
index 000000000..a2e67d1fa
Binary files /dev/null and b/documents/ITDG/maps/test-R400.tms/17/23306/45262.png differ
diff --git a/documents/ITDG/maps/test-R400.tms/17/23307/45260.png b/documents/ITDG/maps/test-R400.tms/17/23307/45260.png
new file mode 100644
index 000000000..88f25b714
Binary files /dev/null and b/documents/ITDG/maps/test-R400.tms/17/23307/45260.png differ
diff --git a/documents/ITDG/maps/test-R400.tms/tilemapresource.xml b/documents/ITDG/maps/test-R400.tms/tilemapresource.xml
new file mode 100644
index 000000000..5044a5df8
--- /dev/null
+++ b/documents/ITDG/maps/test-R400.tms/tilemapresource.xml
@@ -0,0 +1,23 @@
+
+
+ R400_imagery_wgs84_clipped_noalpha.tif
+
+ EPSG:4326
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/documents/ITDG/metrics/CPG_Metrics.csv b/documents/ITDG/metrics/CPG_Metrics.csv
new file mode 100644
index 000000000..9309a4aa5
--- /dev/null
+++ b/documents/ITDG/metrics/CPG_Metrics.csv
@@ -0,0 +1,95 @@
+"title","index","type","setLayerOwned","defaultValue","options"
+"Understanding of Commander’s Intent.","4","Range","undefined","0","[0,4,1,{'0':'N/A','1':'Highly Ineffective','2':'Ineffective','3':'Effective','4':'Highly Effective'}]"
+"Achievement of Commander’s Intent.","5","Range","undefined","0","[0,4,1,{'0':'N/A','1':'Highly Ineffective','2':'Ineffective','3':'Effective','4':'Highly Effective'}]"
+"Focus on mission accomplishment.","6","Range","undefined","0","[0,4,1,{'0':'N/A','1':'Highly Ineffective','2':'Ineffective','3':'Effective','4':'Highly Effective'}]"
+"Use of assets appropriate for the situation.","7","Range","undefined","0","[0,4,1,{'0':'N/A','1':'Highly Ineffective','2':'Ineffective','3':'Effective','4':'Highly Effective'}]"
+"Use of assets in accordance with their capabilities.","8","Range","undefined","0","[0,4,1,{'0':'N/A','1':'Highly Ineffective','2':'Ineffective','3':'Effective','4':'Highly Effective'}]"
+"Coordination of supporting arms.","9","Range","undefined","0","[0,4,1,{'0':'N/A','1':'Highly Ineffective','2':'Ineffective','3':'Effective','4':'Highly Effective'}]"
+"Use of ALL available assets relevant to the fight (including requests for assets).","10","Range","undefined","0","[0,4,1,{'0':'N/A','1':'Highly Ineffective','2':'Ineffective','3':'Effective','4':'Highly Effective'}]"
+"Use of non-traditional assets or creative use of assets.","11","Range","undefined","0","[0,4,1,{'0':'N/A','1':'Highly Ineffective','2':'Ineffective','3':'Effective','4':'Highly Effective'}]"
+"Control of key terrain.","12","Range","undefined","0","[0,4,1,{'0':'N/A','1':'Highly Ineffective','2':'Ineffective','3':'Effective','4':'Highly Effective'}]"
+"Use of terrain to constrain the enemy.","13","Range","undefined","0","[0,4,1,{'0':'N/A','1':'Highly Ineffective','2':'Ineffective','3':'Effective','4':'Highly Effective'}]"
+"Use of terrain for concealment.","14","Range","undefined","0","[0,4,1,{'0':'N/A','1':'Highly Ineffective','2':'Ineffective','3':'Effective','4':'Highly Effective'}]"
+"Use of terrain for protection/cover.","15","Range","undefined","0","[0,4,1,{'0':'N/A','1':'Highly Ineffective','2':'Ineffective','3':'Effective','4':'Highly Effective'}]"
+"Assessment of enemy objectives.","16","Range","undefined","0","[0,4,1,{'0':'N/A','1':'Highly Ineffective','2':'Ineffective','3':'Effective','4':'Highly Effective'}]"
+"Assessment of enemy positions.","17","Range","undefined","0","[0,4,1,{'0':'N/A','1':'Highly Ineffective','2':'Ineffective','3':'Effective','4':'Highly Effective'}]"
+"Assessment of enemy scheme of maneuver.","18","Range","undefined","0","[0,4,1,{'0':'N/A','1':'Highly Ineffective','2':'Ineffective','3':'Effective','4':'Highly Effective'}]"
+"Assessment of enemy disposition and capabilities.","19","Range","undefined","0","[0,4,1,{'0':'N/A','1':'Highly Ineffective','2':'Ineffective','3':'Effective','4':'Highly Effective'}]"
+"Assessment of how the enemy would expect his actions, and ability to stay 2 moves ahead.","20","Range","undefined","0","[0,4,1,{'0':'N/A','1':'Highly Ineffective','2':'Ineffective','3':'Effective','4':'Highly Effective'}]"
+"Operated inside the opponent’s OODA loop.","21","Range","undefined","0","[0,4,1,{'0':'N/A','1':'Highly Ineffective','2':'Ineffective','3':'Effective','4':'Highly Effective'}]"
+"Leveraged the element of surprise.","22","Range","undefined","0","[0,4,1,{'0':'N/A','1':'Highly Ineffective','2':'Ineffective','3':'Effective','4':'Highly Effective'}]"
+"Assessment of time required to maneuver assets.","23","Range","undefined","0","[0,4,1,{'0':'N/A','1':'Highly Ineffective','2':'Ineffective','3':'Effective','4':'Highly Effective'}]"
+"Appropriate orchestration and timing of asset employment.","24","Range","undefined","0","[0,4,1,{'0':'N/A','1':'Highly Ineffective','2':'Ineffective','3':'Effective','4':'Highly Effective'}]"
+"Planned for contingencies.","25","Range","undefined","0","[0,4,1,{'0':'N/A','1':'Highly Ineffective','2':'Ineffective','3':'Effective','4':'Highly Effective'}]"
+"Adapted to situation changes.","26","Range","undefined","0","[0,4,1,{'0':'N/A','1':'Highly Ineffective','2':'Ineffective','3':'Effective','4':'Highly Effective'}]"
+"Provided necessary information to subordinates.","27","Range","undefined","0","[0,4,1,{'0':'N/A','1':'Highly Ineffective','2':'Ineffective','3':'Effective','4':'Highly Effective'}]"
+"Kept Higher informed.","28","Range","undefined","0","[0,4,1,{'0':'N/A','1':'Highly Ineffective','2':'Ineffective','3':'Effective','4':'Highly Effective'}]"
+"Kept adjacent units informed.","29","Range","undefined","0","[0,4,1,{'0':'N/A','1':'Highly Ineffective','2':'Ineffective','3':'Effective','4':'Highly Effective'}]"
+"Communicated in a timely manner or at the right times.","30","Range","undefined","0","[0,4,1,{'0':'N/A','1':'Highly Ineffective','2':'Ineffective','3':'Effective','4':'Highly Effective'}]"
+"Effectively described the situation to subordinates.","31","Range","undefined","0","[0,4,1,{'0':'N/A','1':'Highly Ineffective','2':'Ineffective','3':'Effective','4':'Highly Effective'}]"
+"Effectively described the situation to Higher.","32","Range","undefined","0","[0,4,1,{'0':'N/A','1':'Highly Ineffective','2':'Ineffective','3':'Effective','4':'Highly Effective'}]"
+"Pushed through resistance with determination.","33","Range","undefined","0","[0,4,1,{'0':'N/A','1':'Highly Ineffective','2':'Ineffective','3':'Effective','4':'Highly Effective'}]"
+"Showed a will to take action despite uncertainty.","34","Range","undefined","0","[0,4,1,{'0':'N/A','1':'Highly Ineffective','2':'Ineffective','3':'Effective','4':'Highly Effective'}]"
+"Showed he knew what was going on, especially with the enemy, throughout the mission.","35","Range","undefined","0","[0,4,1,{'0':'N/A','1':'Highly Ineffective','2':'Ineffective','3':'Effective','4':'Highly Effective'}]"
+"Overall, acted decisively.","36","Range","undefined","0","[0,4,1,{'0':'N/A','1':'Highly Ineffective','2':'Ineffective','3':'Effective','4':'Highly Effective'}]"
+"Overall, performed effectively.","37","Range","undefined","0","[0,4,1,{'0':'N/A','1':'Highly Ineffective','2':'Ineffective','3':'Effective','4':'Highly Effective'}]"
+"I understood my Commander’s Intent.","38","Range","undefined","0","[0,4,1,{'0':'N/A','1':'Strongly Disagree','2':'Disagree','3':'Agree','4':'Strongly Agree'}]"
+"I achieved my Commander’s Intent.","39","Range","undefined","0","[0,4,1,{'0':'N/A','1':'Strongly Disagree','2':'Disagree','3':'Agree','4':'Strongly Agree'}]"
+"I stayed focused on my mission even when I could’ve been distracted by other tasks or events.","40","Range","undefined","0","[0,4,1,{'0':'N/A','1':'Strongly Disagree','2':'Disagree','3':'Agree','4':'Strongly Agree'}]"
+"I brought the right assets to bear.","41","Range","undefined","0","[0,4,1,{'0':'N/A','1':'Strongly Disagree','2':'Disagree','3':'Agree','4':'Strongly Agree'}]"
+"I understood my assets’ capabilities well enough to employ them correctly.","42","Range","undefined","0","[0,4,1,{'0':'N/A','1':'Strongly Disagree','2':'Disagree','3':'Agree','4':'Strongly Agree'}]"
+"I coordinated supporting arms effectively.","43","Range","undefined","0","[0,4,1,{'0':'N/A','1':'Strongly Disagree','2':'Disagree','3':'Agree','4':'Strongly Agree'}]"
+"I used all the assets available to me.","44","Range","undefined","0","[0,4,1,{'0':'N/A','1':'Strongly Disagree','2':'Disagree','3':'Agree','4':'Strongly Agree'}]"
+"I was creative with the assets I employed or how I chose to employ them.","45","Range","undefined","0","[0,4,1,{'0':'N/A','1':'Strongly Disagree','2':'Disagree','3':'Agree','4':'Strongly Agree'}]"
+"I controlled the key terrain.","46","Range","undefined","0","[0,4,1,{'0':'N/A','1':'Strongly Disagree','2':'Disagree','3':'Agree','4':'Strongly Agree'}]"
+"I forced my opponent into the terrain of my choosing.","47","Range","undefined","0","[0,4,1,{'0':'N/A','1':'Strongly Disagree','2':'Disagree','3':'Agree','4':'Strongly Agree'}]"
+"I used the terrain to conceal my movements.","48","Range","undefined","0","[0,4,1,{'0':'N/A','1':'Strongly Disagree','2':'Disagree','3':'Agree','4':'Strongly Agree'}]"
+"I used the terrain to protect my assets.","49","Range","undefined","0","[0,4,1,{'0':'N/A','1':'Strongly Disagree','2':'Disagree','3':'Agree','4':'Strongly Agree'}]"
+"I was right about my opponent’s objectives.","50","Range","undefined","0","[0,4,1,{'0':'N/A','1':'Strongly Disagree','2':'Disagree','3':'Agree','4':'Strongly Agree'}]"
+"I was right about where my opponent would position his forces.","51","Range","undefined","0","[0,4,1,{'0':'N/A','1':'Strongly Disagree','2':'Disagree','3':'Agree','4':'Strongly Agree'}]"
+"I was right about how my opponent would maneuver.","52","Range","undefined","0","[0,4,1,{'0':'N/A','1':'Strongly Disagree','2':'Disagree','3':'Agree','4':'Strongly Agree'}]"
+"I was right about the strength and capabilities of my opponent.","53","Range","undefined","0","[0,4,1,{'0':'N/A','1':'Strongly Disagree','2':'Disagree','3':'Agree','4':'Strongly Agree'}]"
+"I was right about how my opponent would expect me to act, and I stayed 2 moves ahead of him.","54","Range","undefined","0","[0,4,1,{'0':'N/A','1':'Strongly Disagree','2':'Disagree','3':'Agree','4':'Strongly Agree'}]"
+"I was inside my opponent’s OODA loop.","55","Range","undefined","0","[0,4,1,{'0':'N/A','1':'Strongly Disagree','2':'Disagree','3':'Agree','4':'Strongly Agree'}]"
+"I took my opponent by surprise.","56","Range","undefined","0","[0,4,1,{'0':'N/A','1':'Strongly Disagree','2':'Disagree','3':'Agree','4':'Strongly Agree'}]"
+"I was in the right place at the right time.","57","Range","undefined","0","[0,4,1,{'0':'N/A','1':'Strongly Disagree','2':'Disagree','3':'Agree','4':'Strongly Agree'}]"
+"I orchestrated and timed my use of assets effectively.","58","Range","undefined","0","[0,4,1,{'0':'N/A','1':'Strongly Disagree','2':'Disagree','3':'Agree','4':'Strongly Agree'}]"
+"My plan had built-in flexibility.","59","Range","undefined","0","[0,4,1,{'0':'N/A','1':'Strongly Disagree','2':'Disagree','3':'Agree','4':'Strongly Agree'}]"
+"I adapted my approach at the right point in time.","60","Range","undefined","0","[0,4,1,{'0':'N/A','1':'Strongly Disagree','2':'Disagree','3':'Agree','4':'Strongly Agree'}]"
+"I told my subordinates what they needed to know to be effective.","61","Range","undefined","0","[0,4,1,{'0':'N/A','1':'Strongly Disagree','2':'Disagree','3':'Agree','4':'Strongly Agree'}]"
+"I told Higher what was important for him to know.","62","Range","undefined","0","[0,4,1,{'0':'N/A','1':'Strongly Disagree','2':'Disagree','3':'Agree','4':'Strongly Agree'}]"
+"I told my adjacent units what they needed to know.","63","Range","undefined","0","[0,4,1,{'0':'N/A','1':'Strongly Disagree','2':'Disagree','3':'Agree','4':'Strongly Agree'}]"
+"I provided information in a timely manner.","64","Range","undefined","0","[0,4,1,{'0':'N/A','1':'Strongly Disagree','2':'Disagree','3':'Agree','4':'Strongly Agree'}]"
+"I described what I thought the enemy was doing to my subordinates.","65","Range","undefined","0","[0,4,1,{'0':'N/A','1':'Strongly Disagree','2':'Disagree','3':'Agree','4':'Strongly Agree'}]"
+"I described what I thought the enemy was doing to Higher.","66","Range","undefined","0","[0,4,1,{'0':'N/A','1':'Strongly Disagree','2':'Disagree','3':'Agree','4':'Strongly Agree'}]"
+"I pushed through resistance to achieve my goal.","67","Range","undefined","0","[0,4,1,{'0':'N/A','1':'Strongly Disagree','2':'Disagree','3':'Agree','4':'Strongly Agree'}]"
+"I took action even when faced with uncertainty.","68","Range","undefined","0","[0,4,1,{'0':'N/A','1':'Strongly Disagree','2':'Disagree','3':'Agree','4':'Strongly Agree'}]"
+"I knew what was happening, especially with my opponent, throughout the mission.","69","Range","undefined","0","[0,4,1,{'0':'N/A','1':'Strongly Disagree','2':'Disagree','3':'Agree','4':'Strongly Agree'}]"
+"Overall, I acted decisively.","70","Range","undefined","0","[0,4,1,{'0':'N/A','1':'Strongly Disagree','2':'Disagree','3':'Agree','4':'Strongly Agree'}]"
+"Overall, my decisions and actions were effective.","71","Range","undefined","0","[0,4,1,{'0':'N/A','1':'Strongly Disagree','2':'Disagree','3':'Agree','4':'Strongly Agree'}]"
+"My opponent was able to achieve his mission objective.","72","Range","undefined","0","[0,4,1,{'0':'N/A','1':'Strongly Disagree','2':'Disagree','3':'Agree','4':'Strongly Agree'}]"
+"My opponent was focused on his mission even when they could’ve been distracted by other tasks or events.","73","Range","undefined","0","[0,4,1,{'0':'N/A','1':'Strongly Disagree','2':'Disagree','3':'Agree','4':'Strongly Agree'}]"
+"My opponent employed assets that were stronger than mine.","74","Range","undefined","0","[0,4,1,{'0':'N/A','1':'Strongly Disagree','2':'Disagree','3':'Agree','4':'Strongly Agree'}]"
+"My opponent overwhelmed me with firepower.","75","Range","undefined","0","[0,4,1,{'0':'N/A','1':'Strongly Disagree','2':'Disagree','3':'Agree','4':'Strongly Agree'}]"
+"My opponent overwhelmed me with the number of threats to which I had to respond.","76","Range","undefined","0","[0,4,1,{'0':'N/A','1':'Strongly Disagree','2':'Disagree','3':'Agree','4':'Strongly Agree'}]"
+"My opponent used their assets cleverly, in a way I didn’t anticipate.","77","Range","undefined","0","[0,4,1,{'0':'N/A','1':'Strongly Disagree','2':'Disagree','3':'Agree','4':'Strongly Agree'}]"
+"My opponent controlled the key terrain.","78","Range","undefined","0","[0,4,1,{'0':'N/A','1':'Strongly Disagree','2':'Disagree','3':'Agree','4':'Strongly Agree'}]"
+"My opponent forced me into terrain of their choosing.","79","Range","undefined","0","[0,4,1,{'0':'N/A','1':'Strongly Disagree','2':'Disagree','3':'Agree','4':'Strongly Agree'}]"
+"My opponent used terrain to conceal their movements.","80","Range","undefined","0","[0,4,1,{'0':'N/A','1':'Strongly Disagree','2':'Disagree','3':'Agree','4':'Strongly Agree'}]"
+"My opponent used terrain to protect their assets.","81","Range","undefined","0","[0,4,1,{'0':'N/A','1':'Strongly Disagree','2':'Disagree','3':'Agree','4':'Strongly Agree'}]"
+"My opponent correctly anticipated my objectives.","82","Range","undefined","0","[0,4,1,{'0':'N/A','1':'Strongly Disagree','2':'Disagree','3':'Agree','4':'Strongly Agree'}]"
+"My opponent anticipated where I would position my forces.","83","Range","undefined","0","[0,4,1,{'0':'N/A','1':'Strongly Disagree','2':'Disagree','3':'Agree','4':'Strongly Agree'}]"
+"My opponent anticipated how I would maneuver.","84","Range","undefined","0","[0,4,1,{'0':'N/A','1':'Strongly Disagree','2':'Disagree','3':'Agree','4':'Strongly Agree'}]"
+"My opponent anticipated my strengths and capabilities.","85","Range","undefined","0","[0,4,1,{'0':'N/A','1':'Strongly Disagree','2':'Disagree','3':'Agree','4':'Strongly Agree'}]"
+"My opponent figured out how I expected him to act, and he stayed 2 moves ahead of me.","86","Range","undefined","0","[0,4,1,{'0':'N/A','1':'Strongly Disagree','2':'Disagree','3':'Agree','4':'Strongly Agree'}]"
+"My opponent was inside my OODA loop.","87","Range","undefined","0","[0,4,1,{'0':'N/A','1':'Strongly Disagree','2':'Disagree','3':'Agree','4':'Strongly Agree'}]"
+"My opponent took me by surprise.","88","Range","undefined","0","[0,4,1,{'0':'N/A','1':'Strongly Disagree','2':'Disagree','3':'Agree','4':'Strongly Agree'}]"
+"My opponent was in the right place at the right time.","89","Range","undefined","0","[0,4,1,{'0':'N/A','1':'Strongly Disagree','2':'Disagree','3':'Agree','4':'Strongly Agree'}]"
+"My opponent orchestrated and timed their use of assets effectively.","90","Range","undefined","0","[0,4,1,{'0':'N/A','1':'Strongly Disagree','2':'Disagree','3':'Agree','4':'Strongly Agree'}]"
+"My opponent was prepared to be flexible.","91","Range","undefined","0","[0,4,1,{'0':'N/A','1':'Strongly Disagree','2':'Disagree','3':'Agree','4':'Strongly Agree'}]"
+"My opponent adapted his approach at the right point in time.","92","Range","undefined","0","[0,4,1,{'0':'N/A','1':'Strongly Disagree','2':'Disagree','3':'Agree','4':'Strongly Agree'}]"
+"My opponent showed resolve by pushing through the resistance I put in his path.","93","Range","undefined","0","[0,4,1,{'0':'N/A','1':'Strongly Disagree','2':'Disagree','3':'Agree','4':'Strongly Agree'}]"
+"My opponent took action even when faced with uncertainty.","94","Range","undefined","0","[0,4,1,{'0':'N/A','1':'Strongly Disagree','2':'Disagree','3':'Agree','4':'Strongly Agree'}]"
+"My opponent knew what was happening, especially my actions and intent, throughout the mission.","95","Range","undefined","0","[0,4,1,{'0':'N/A','1':'Strongly Disagree','2':'Disagree','3':'Agree','4':'Strongly Agree'}]"
+"Overall, my opponent acted decisively.","96","Range","undefined","0","[0,4,1,{'0':'N/A','1':'Strongly Disagree','2':'Disagree','3':'Agree','4':'Strongly Agree'}]"
+"Overall, my opponent’s decisions and actions were effective.","97","Range","undefined","0","[0,4,1,{'0':'N/A','1':'Strongly Disagree','2':'Disagree','3':'Agree','4':'Strongly Agree'}]"
\ No newline at end of file
diff --git a/documents/ITDG/metrics/TBS_BOC_Metrics.csv b/documents/ITDG/metrics/TBS_BOC_Metrics.csv
new file mode 100644
index 000000000..69eb8f981
--- /dev/null
+++ b/documents/ITDG/metrics/TBS_BOC_Metrics.csv
@@ -0,0 +1,5 @@
+"title","index","type","setLayerOwned","defaultValue","options"
+"Student Orders","0","Long Text","undefined","","null"
+"Instructor Rating","1","Range","undefined","","[0,10,1,{'0':'N/A','1':'Unsat','2':'Unsat','3':'Marginal','4':'Marginal','5':'Acceptable','6':'Acceptable','7':'Capable','8':'Capable','9':'Outstanding','10':'Outstanding'}]"
+"Instructor Grade","2","Text","undefined","","null"
+"Instructor Observations","3","Long Text","undefined","","null"
\ No newline at end of file
diff --git a/documents/ITDG/scenarioTemplate/saveState.vwf.config.json b/documents/ITDG/scenarioTemplate/saveState.vwf.config.json
new file mode 100644
index 000000000..a1e5cfb61
--- /dev/null
+++ b/documents/ITDG/scenarioTemplate/saveState.vwf.config.json
@@ -0,0 +1 @@
+{"info":{"title":"Scenario Template"},"model":{"vwf/model/javascript":"","vwf/model/kineticjs":"","vwf/model/mil-sym":"","vwf/model/object":"","nodriver":""},"view":{"vwf/view/document":"","vwf/view/touch":"","vwf/view/kineticjs":"","vwf/view/mil-sym":""}}
\ No newline at end of file
diff --git a/documents/ITDG/scenarioTemplate/saveState.vwf.json b/documents/ITDG/scenarioTemplate/saveState.vwf.json
new file mode 100644
index 000000000..7e10514e9
--- /dev/null
+++ b/documents/ITDG/scenarioTemplate/saveState.vwf.json
@@ -0,0 +1 @@
+{"configuration":{"log-level":"info","random-seed":1466540519030,"randomize-ids":true,"humanize-ids":true,"preserve-script-closures":false,"load-timeout":120},"kernel":{"time":0},"nodes":[{"patches":"http://vwf.example.com/clients.vwf","sequence":7,"random":{"s0":0.24205980077385902,"s1":0.9761656543705612,"s2":0.9434323732275516,"c":11304},"children":{}},{"patches":"/ITDG/index.vwf","properties":{"unitIconDefaults":{"shadowColor":"rgba( 1, 1, 1, 0.5 )","shadowBlur":10,"shadowOffsetX":8,"shadowOffsetY":8,"shadowEnabled":false}},"children":{"scenarioController":{"patches":"/ITDG/index.vwf:3-47-scenarioController","sequence":4,"random":{"s0":0.4123580085579306,"s1":0.9537082586903125,"s2":0.22647805558517575,"c":582588},"properties":{},"children":{}},"participants":{"patches":"/ITDG/index.vwf:5-08-participants","sequence":1,"random":{"s0":0.6633416542317718,"s1":0.6727178883738816,"s2":0.3024811027571559,"c":1237838},"children":{}}}}],"annotations":{"1":"application"},"queue":{"time":0,"queue":[]}}
\ No newline at end of file
diff --git a/external/README.txt b/external/README.txt
new file mode 100644
index 000000000..ff1e64090
--- /dev/null
+++ b/external/README.txt
@@ -0,0 +1 @@
+Place external applications in "external" folder.
diff --git a/lib/nodejs/admin.js b/lib/nodejs/admin.js
index 2b0c20c1e..8a8a9a3aa 100755
--- a/lib/nodejs/admin.js
+++ b/lib/nodejs/admin.js
@@ -232,7 +232,7 @@ function HandleAdminConfig( request, response, parsedRequest ) {
if ( ( parsedRequest[ 'public_path' ] ) && ( parsedRequest[ 'application' ] ) && ( request.method == "GET" ) ) {
var filenameRoot = helpers.JoinPath( global.applicationRoot, parsedRequest[ 'public_path' ], parsedRequest[ 'application' ] );
if ( helpers.IsFile( filenameRoot + ".config.yaml" ) ) {
- serve.YAML( ( filenameRoot + ".config.yaml" ).replace( /\//g, libpath.sep ), response, url.parse( request.url, true ) );
+ serve.YAML( request, ( filenameRoot + ".config.yaml" ).replace( /\//g, libpath.sep ), response, url.parse( request.url, true ) );
return true;
}
else if ( helpers.IsFile( filenameRoot + ".config.json" ) ) {
@@ -242,6 +242,7 @@ function HandleAdminConfig( request, response, parsedRequest ) {
else {
// If there is no config file, return an empty string to prevent 404 errors
serve.JSON("", response);
+ return true;
}
}
return false;
@@ -267,9 +268,9 @@ function HandleAdminChrome( request, response, parsedRequest ) {
return false;
}
-// The Serve function in admin takes in the nodeJS request and nnodeJS response as well as
+// The Serve function in admin takes in the nodeJS request and nodeJS response as well as
// the parsedRequest information.
-// It tests if the request is actually an admin request, and if calls an appropriate handler
+// It tests if the request is actually an admin request, and if so, calls an appropriate handler
// to serve the appropriate response.
// If the request is succesfully served, it returns TRUE, if the request is not succesfully
// served (whether due to a file not being present, the request being malformed, or simply it
diff --git a/lib/nodejs/application.js b/lib/nodejs/application.js
index 7f6856baa..4f0fe6b1b 100755
--- a/lib/nodejs/application.js
+++ b/lib/nodejs/application.js
@@ -5,7 +5,8 @@
var servehandler = require( './serve-handler' ),
helpers = require( './helpers' ),
persistence = require( './persistence' ),
- admin = require( './admin' );
+ admin = require( './admin' ),
+ stream = require( './stream' );
// Requests are attempted to be served as if they pertain to a specific application.
// If any of these attempts succesfully serve the request, true is returned.
@@ -32,11 +33,15 @@ function Serve( request, response, parsedRequest ) {
if ( servehandler.Component( request, response, helpers.JoinPath( global.applicationRoot, parsedRequest[ 'public_path' ], parsedRequest[ 'private_path' ] ) ) ) {
return true;
}
+ // Test if mediaServer can server the request.
+ if ( stream.Serve( request, response, parsedRequest ) ) {
+ return true;
+ }
// If the request was still not served, try and see if the persistence functionality will serve this request.
if ( persistence.Serve( request, response, parsedRequest ) ) {
return true;
}
- // Finally, test if the admin functionality can serve this request.
+ // Test if the admin functionality can serve this request.
if ( admin.Serve( request, response, parsedRequest ) ) {
return true;
}
diff --git a/lib/nodejs/authentication.js b/lib/nodejs/authentication.js
new file mode 100644
index 000000000..fd3069efc
--- /dev/null
+++ b/lib/nodejs/authentication.js
@@ -0,0 +1,150 @@
+// TDG authentication scheme.
+//
+// A valid login is required for all routes. Any username is accepted, but instructors must provide
+// the instructor password. A VWF `client.vwf` descriptor is published to the session to describe
+// the user in the VWF application.
+
+var passport = require( "passport" ),
+ passportLocal = require( "passport-local" ),
+ config = require( "config" ),
+ path = require( "path" );
+
+// Passport strategy options.
+
+var strategyOptions = {
+ passReqToCallback: true, // Call the strategy callback as `cb( req, ... )` instead of `cb( ... )`
+ usernameField: "last_name", // A non-empty field to appease `passport-local`, but we ignore `username`
+ passwordField: "last_name", // A non-empty field, and we ignore `password`; the actual password may be empty
+};
+
+// Create and attach the Passport strategy object.
+
+var strategy = new passportLocal( strategyOptions, function( req, username, password, done ) {
+
+ // Build the user object from the form properties.
+
+ var user = {
+ last_name:
+ req.body.last_name || req.query.last_name,
+ first_name:
+ req.body.first_name || req.query.first_name,
+ middle_initial:
+ req.body.middle_initial || req.query.middle_initial,
+ instructor:
+ req.body.instructor || req.query.instructor,
+ };
+
+ // Get the actual password from the form.
+
+ password = req.body.password || req.query.password;
+
+ // Check the password if one is configured. Instructors must provide a password.
+
+ var authenticated = ! config.has( "tdg.password" ) || ! config.get( "tdg.password" ) ||
+ password === config.get( "tdg.password" );
+
+ // Return the result. For successful logins, return the user object. The user object will be
+ // attached to the request at `req.user`.
+ //
+ // For login errors, return `false` for `user` and provide an error message. If an error occurs
+ // while performing the lookup, return the error object.
+
+ if ( user.instructor && ! authenticated ) {
+ return done( null, false, { message: 'Incorrect password' } );
+ } else {
+ return done( null, user );
+ }
+
+} );
+
+passport.use( strategy );
+
+// Convert the user object to a reference to be stored in the session. Without a user database, this
+// can be the user object. With a local user database, it should be the user id in the database.
+//
+// Also attach a VWF `client.vwf` descriptor to the session to describe the user for the VWF
+// application.
+
+passport.serializeUser( function( req, user, done ) {
+
+ req.session.vwf = req.session.vwf || {};
+
+ req.session.vwf.client = {
+ properties: {
+ last_name:
+ user.last_name,
+ first_name:
+ user.first_name,
+ middle_initial:
+ user.middle_initial,
+ instructor:
+ user.instructor !== undefined ? !! user.instructor : undefined,
+ }
+ };
+
+ done( null, user );
+
+} );
+
+// Convert a user reference in the session to a user object. Without a user database, the session
+// data is the user object. With a local user database, look up the user from an id in the session.
+
+passport.deserializeUser( function( req, id, done ) {
+ done( null, id );
+} );
+
+// Create a router to contain the Passport middleware and login/logout routes.
+
+var router = require( "express" ).Router();
+
+// Passport and the Passport `session` module.
+
+router.use( passport.initialize() );
+router.use( passport.authenticate( "session" ) );
+
+// Show a login form at `/login`.
+
+router.get( "/login", function( req, res ) {
+ res.sendFile( path.join( __dirname, "../../support/lobby", "login.html" ) );
+} );
+
+// Receive a login request and attempt to authenticate.
+//
+// The URLs would need the base URL prepended if the application is not mounted at `/`. Since the
+// authentication middleware is created outside the request, we don't have access to `baseUrl` to
+// do this.
+
+router.post( "/login", passport.authenticate( "local", {
+ successReturnToOrRedirect: /* req.baseUrl + */ "/",
+ failureRedirect: /* req.baseUrl + */ "/login",
+ failureFlash: true,
+} ) );
+
+// Receive a logout request and log out. Also remove the VWF `client.vwf` descriptor from the
+// session.
+
+router.post( "/logout", function( req, res ) {
+ req.logout();
+ req.session.vwf && delete req.session.vwf.client;
+ res.redirect( req.baseUrl + "/login" );
+} );
+
+// Enforce authentication rules. This implementation requires a valid login for all routes except
+// for `/login` and `/logout`.
+
+router.use( function( req, res, next ) {
+ if ( req.isUnauthenticated() ) {
+ if ( req.accepts( "html", "json" ) === "html" ) {
+ req.session.returnTo = req.baseUrl + req.url;
+ res.redirect( req.baseUrl + "/login" );
+ } else {
+ res.sendStatus( 401 );
+ }
+ } else {
+ next();
+ }
+} );
+
+// Export the router.
+
+module.exports = router;
diff --git a/lib/nodejs/authentication/passport-http.js b/lib/nodejs/authentication/passport-http.js
new file mode 100644
index 000000000..3e8157724
--- /dev/null
+++ b/lib/nodejs/authentication/passport-http.js
@@ -0,0 +1,65 @@
+// Example authentication module implemented with Passport and HTTP Basic authentication.
+//
+// A valid login is required for all routes, but any username and password are accepted. Hooks for
+// querying a user database are shown. A VWF `client.vwf` descriptor is published to the session to
+// describe the user in the VWF application.
+//
+// Add the NPM modules `passport` and `passport-http` to support this method.
+
+var passport = require( "passport" ),
+ passportHTTP = require( "passport-http" );
+
+// Create and attach the Passport strategy object.
+
+var strategy = new passportHTTP.BasicStrategy( function( username, password, done ) {
+
+ // Given `username` and `password` from a login form, look up the user in a local database.
+
+ var user = { username: username };
+
+ // Return the result. For successful logins, return the user object. The user object will be
+ // attached to the request at `req.user`.
+ //
+ // For login errors, return `false` for `user` and provide an error message. If an error occurs
+ // while performing the lookup, return the error object.
+
+ if ( false /* lookup error */ ) {
+ return done( {} /* err */ );
+ } else if ( false /* bad user */ ) {
+ return done( null, false, { message: 'Incorrect username' } );
+ } else if ( false /* bad password */ ) {
+ return done( null, false, { message: 'Incorrect password' } );
+ } else {
+ return done( null, user );
+ }
+
+} );
+
+passport.use( strategy );
+
+// Convert the user object to a reference to be stored in the session. Without a user database, this
+// can be the user object. With a local user database, it should be the user id in the database.
+//
+// Also attach a VWF `client.vwf` descriptor to the session to describe the user for the VWF
+// application.
+
+passport.serializeUser( function( req, user, done ) {
+ req.session.vwf = req.session.vwf || {};
+ req.session.vwf.client = { properties: { username: user.username } };
+ done( null, user );
+} );
+
+// Convert a user reference in the session to a user object. Without a user database, the session
+// data is the user object. With a local user database, look up the user from an id in the session.
+
+passport.deserializeUser( function( req, id, done ) {
+ done( null, id );
+} );
+
+// Export the Passport initializer, `session` module, and HTTP Basic strategy module.
+
+module.exports = [
+ passport.initialize(),
+ passport.authenticate( "session" ),
+ passport.authenticate( "basic" ),
+];
diff --git a/lib/nodejs/authentication/passport-local.js b/lib/nodejs/authentication/passport-local.js
new file mode 100644
index 000000000..e14fae997
--- /dev/null
+++ b/lib/nodejs/authentication/passport-local.js
@@ -0,0 +1,126 @@
+// Example authentication module implemented with Passport and login forms.
+//
+// A valid login is required for all routes, but any username and password are accepted. Hooks for
+// querying a user database are shown. A VWF `client.vwf` descriptor is published to the session to
+// describe the user in the VWF application.
+//
+// Add the NPM modules `passport` and `passport-local` to support this method.
+
+var passport = require( "passport" ),
+ passportLocal = require( "passport-local" );
+
+// Create and attach the Passport strategy object.
+
+var strategy = new passportLocal( function( username, password, done ) {
+
+ // Given `username` and `password` from a login form, look up the user in a local database.
+
+ var user = { username: username };
+
+ // Return the result. For successful logins, return the user object. The user object will be
+ // attached to the request at `req.user`.
+ //
+ // For login errors, return `false` for `user` and provide an error message. If an error occurs
+ // while performing the lookup, return the error object.
+
+ if ( false /* lookup error */ ) {
+ return done( {} /* err */ );
+ } else if ( false /* bad user */ ) {
+ return done( null, false, { message: 'Incorrect username' } );
+ } else if ( false /* bad password */ ) {
+ return done( null, false, { message: 'Incorrect password' } );
+ } else {
+ return done( null, user );
+ }
+
+} );
+
+passport.use( strategy );
+
+// Convert the user object to a reference to be stored in the session. Without a user database, this
+// can be the user object. With a local user database, it should be the user id in the database.
+//
+// Also attach a VWF `client.vwf` descriptor to the session to describe the user for the VWF
+// application.
+
+passport.serializeUser( function( req, user, done ) {
+ req.session.vwf = req.session.vwf || {};
+ req.session.vwf.client = { properties: { username: user.username } };
+ done( null, user );
+} );
+
+// Convert a user reference in the session to a user object. Without a user database, the session
+// data is the user object. With a local user database, look up the user from an id in the session.
+
+passport.deserializeUser( function( req, id, done ) {
+ done( null, id );
+} );
+
+// Create a router to contain the Passport middleware and login/logout routes.
+
+var router = require( "express" ).Router();
+
+// Passport and the Passport `session` module.
+
+router.use( passport.initialize() );
+router.use( passport.authenticate( "session" ) );
+
+// Show a login form at `/login`.
+
+router.get( "/login", function( req, res ) {
+ res.send(
+ "
"
+ );
+} );
+
+// Receive a login request and attempt to authenticate.
+//
+// The URLs would need the base URL prepended if the application is not mounted at `/`. Since the
+// authentication middleware is created outside the request, we don't have access to `baseUrl` to
+// do this.
+
+router.post( "/login", passport.authenticate( "local", {
+ successReturnToOrRedirect: /* req.baseUrl + */ "/",
+ failureRedirect: /* req.baseUrl + */ "/login",
+ failureFlash: true,
+} ) );
+
+// Show a logout form at `/logout`.
+
+router.get( "/logout", function( req, res ) {
+ res.send(
+ ""
+ );
+} );
+
+// Receive a logout request and log out. Also remove the VWF `client.vwf` descriptor from the
+// session.
+
+router.post( "/logout", function( req, res ) {
+ req.logout();
+ req.session.vwf && delete req.session.vwf.client;
+ res.redirect( req.baseUrl + "/login" );
+} );
+
+// Enforce authentication rules. This implementation requires a valid login for all routes except
+// for `/login` and `/logout`.
+
+router.use( function( req, res, next ) {
+ if ( req.isUnauthenticated() ) {
+ res.redirect( req.baseUrl + "/login" );
+ res.session.returnTo = req.baseUrl + req.url;
+ } else {
+ next();
+ }
+} );
+
+// Export the router.
+
+module.exports = router;
diff --git a/lib/nodejs/file-cache.js b/lib/nodejs/file-cache.js
index 3211a6881..bc7bac054 100755
--- a/lib/nodejs/file-cache.js
+++ b/lib/nodejs/file-cache.js
@@ -21,9 +21,10 @@ function _FileCache( ) {
// Function for determining whether to treat file as binary or text
// based on file extentsion.
- this.getDataType = function ( file ) {
- var type = file.substr( file.lastIndexOf( '.' ) + 1 ).toLowerCase( );
- if ( type === 'js' || type === 'html' || type === 'xml' || type === 'txt' || type === 'xhtml' || type === 'css' ) {
+ this.getDataType = function ( fileextension ) {
+ if ( fileextension === 'js' || fileextension === 'html' ||
+ fileextension === 'xml' || fileextension === 'txt' ||
+ fileextension === 'xhtml' || fileextension === 'css' ) {
return "utf8";
}
else return "binary";
@@ -33,11 +34,22 @@ function _FileCache( ) {
// Asynchronous, returns file to the callback function.
// Passes null to the callback function if there is no such file.
this.getFile = function ( path, callback ) {
+ function getLastModifiedTime ( path ) {
+ try {
+ var stats = fs.statSync(path);
+ return stats.mtime;
+ }
+ catch(err) {
+ console.log('Error finding file: '+err);
+ return null;
+ }
+ }
+
//First, check if we have already served this file
// and have it already cached, if so, return the
// already cached file.
for ( var i = 0; i < this.files.length; i++ ) {
- if( this.files[ i ].path == path ) {
+ if( this.files[ i ].path == path ) {
global.log( 'serving from cache: ' + path, 2 );
callback( this.files[ i ] );
return;
@@ -45,11 +57,26 @@ function _FileCache( ) {
}
// if got here, have no record of this file yet.
- var datatype = this.getDataType( path );
- var file = fs.readFileSync( path );
+ var ext = path.substr( path.lastIndexOf( '.' ) + 1 ).toLowerCase();
+ var datatype = this.getDataType( ext );
+ var file = ext !== 'html' ? fs.readFileSync( path) : fs.readFileSync( path, datatype );
var stats = fs.statSync( path );
if ( file ) {
+ // if the file is html replace all autoversion entries with
+ // path appended by mtime parameter before adding to cache
+ if ( ext === 'html' ) {
+ var dirPath = path.substring( 0, path.lastIndexOf( '\\' ) + 1 );
+ // find script and link references that use ?mtime=timestamp format
+ var fileStr = file.replace(/<((?:script|link)[^>]*(?:src|href)="([^"].*?)[^?]*mtime=)timestamp([^"]*"[^>]*)>/g, function(match, pre, file, post) {
+ file = file.substring(0, file.length-1);
+ var mtime = getLastModifiedTime(dirPath+file);
+ var replaceStr = '<'+pre+(mtime ? mtime.getTime() : '')+post+'>';
+ return replaceStr;
+ });
+ // convert to buffer to keep socket messages small
+ file = new Buffer(fileStr);
+ }
// The file was not already in the cache, but does exist.
// Gzip the file, which is an async process, and callback
// with the gzipped file entry when finished.
diff --git a/lib/nodejs/kml-handler.js b/lib/nodejs/kml-handler.js
new file mode 100644
index 000000000..4916e4c33
--- /dev/null
+++ b/lib/nodejs/kml-handler.js
@@ -0,0 +1,43 @@
+// kml-handler.js
+// This file handles the sending/receiving of KML files between applications
+
+
+var net = require( "net" ),
+ fs = require( "fs" ),
+ formidable = require( "formidable" );
+
+
+
+function sendKML ( req, res, next ) {
+ var form = new formidable.IncomingForm();
+ form.keepExtensions = true;
+ // parse the file and then send response
+ form.parse( req, function( err, fields, files ) {
+ if ( !err ) {
+ var client = new net.Socket();
+ client.on( 'error', function( err ) {
+ console.log( err );
+ } );
+ //connect to the host:port
+ client.connect(fields[ 'port' ], fields[ 'host' ], function() {
+ //send a file to the server
+ var fileStream = fs.createReadStream( files[ 'file' ].path );
+ fileStream.on('error', function( err ){
+ console.log( "Error on file stream: " + err );
+ } );
+
+ fileStream.on( 'open',function() {
+ fileStream.pipe( client );
+ } );
+ res.writeHead( 200, {'content-type': 'text/plain'} );
+ res.end();
+ } );
+ } else {
+ console.log("Error parsing file: " + err);
+ res.writeHead(500);
+ res.end();
+ }
+ });
+}
+
+exports.sendKML = sendKML;
diff --git a/lib/nodejs/lobby.js b/lib/nodejs/lobby.js
new file mode 100644
index 000000000..209ab3395
--- /dev/null
+++ b/lib/nodejs/lobby.js
@@ -0,0 +1,348 @@
+var app = module.exports = require( "express" )();
+
+var bodyParser = require( "body-parser" ),
+ cookieParser = require( "cookie-parser" ),
+ cookieSession = require( "cookie-session" ),
+ crypto = require( "crypto" ),
+ config = require( "config" ),
+ flash = require( "flash" ),
+ express = require( "express" ),
+ Promise = require( "bluebird" ),
+ fs = Promise.promisifyAll( require( "fs-extra" ) ),
+ mkdirp = Promise.promisify( require( "mkdirp" ) ),
+ amqp = require( "amqplib" ),
+ zipFolder = Promise.promisify( require( "zip-folder" ) ),
+ admZip = require( "adm-zip" ),
+ upload = require( "multer" )( { dest: "documents/ITDG/imports/" } ),
+ path = require( "path" ),
+ _ = require( "lodash" );
+
+var authentication = require( "./authentication" ),
+ manifest = require( "./manifest" ),
+ xapi = require( "./xapi" ),
+ helpers = require( "./helpers" ),
+ version = require("../../support/client/lib/version"),
+ kmlHandler = require( "./kml-handler" );
+
+app.use( bodyParser.json() );
+app.use( bodyParser.urlencoded( { extended: false, limit: '50mb' } ) );
+app.use( bodyParser.text() );
+app.use( cookieParser() );
+app.use( cookieSession( { secret: sessionSecret() } ) );
+app.use( flash() );
+app.use( "/favicon.ico", express.static( "public/favicon.ico" ) );
+app.use( "/ONR_Logo.jpg", express.static( "public/ONR_Logo.jpg" ) );
+app.get( "/*.bundle.*", express.static( "support/lobby" ) );
+app.use( "/node_modules/bootstrap/dist", express.static( "support/lobby/node_modules/bootstrap/dist" ) );
+
+app.get( "/version", function( req, res ) {
+ res.json( { version, title: version.getDerivativeVersion() || "ITDG" } );
+} );
+
+app.use( authentication );
+
+app.use( function( req, res, next ) {
+ res.locals.user = req.isAuthenticated() ? req.user : undefined;
+ next();
+} );
+
+app.get( "/", function( req, res ) {
+ res.sendFile( path.join( __dirname, "../../support/lobby", "index.html" ) );
+} );
+
+app.get( "/manifest", function( req, res ) {
+ manifest().then( function( manifest ) {
+ res.json( manifest );
+ } );
+} );
+
+app.get( "/user", function( req, res ) {
+ res.json( req.user );
+} );
+
+app.get( "/map-list", function( req, res ) {
+ var mapDir = "documents/ITDG/maps";
+
+ // Ensure that the map directory exists, then get the list of files in the map directory
+ mkdirp( mapDir )
+ .then( made => fs.readdirAsync( mapDir ) )
+ .then( files => res.send( { mapFilenames: files || [] } ) )
+ .catch( err => next( err ) );
+} );
+
+app.get( "/image-list", function( req, res ) {
+ var imageDir = "documents/ITDG/image";
+
+ // Ensure that the image directory exists, then get the list of files in the image directory
+ mkdirp( imageDir )
+ .then( made => fs.readdirAsync( imageDir ) )
+ .then( files => res.send( { imageFilenames: files || [] } ) )
+ .catch( err => next( err ) );
+} );
+
+app.get( "/sound-list", function( req, res ) {
+ var soundDir = "documents/ITDG/sound";
+
+ // Ensure that the sound directory exists, then get the list of files in the sound directory
+ mkdirp( soundDir )
+ .then( made => fs.readdirAsync( soundDir ) )
+ .then( files => res.send( { soundFilenames: files || [] } ) )
+ .catch( err => next( err ) );
+} );
+
+app.get( "/video-list", function( req, res ) {
+ var videoDir = "documents/ITDG/video";
+
+ // Ensure that the video directory exists, then get the list of files in it
+ mkdirp( videoDir )
+ .then( made => fs.readdirAsync( videoDir ) )
+ .then( files => res.send( { videoFilenames: files || [] } ) )
+ .catch( err => next( err ) );
+} );
+
+app.get( "/tracking-list", function( req, res ) {
+ var trackingDir = "documents/ITDG/tracking";
+
+ // Ensure that the tracking directory exists, then get the list of files in it
+ mkdirp( trackingDir )
+ .then( made => fs.readdirAsync( trackingDir ) )
+ .then( files => res.send( { trackingFilenames: files || [] } ) )
+ .catch( err => next( err ) );
+} );
+
+app.get( "/affiliation-list", function( req, res ) {
+ var affiliationDir = "documents/ITDG/affiliation";
+
+ // Ensure that the affiliation directory exists, then get the list of files in it
+ mkdirp( affiliationDir )
+ .then( made => fs.readdirAsync( affiliationDir ) )
+ .then( files => res.send( { affiliationFilenames: files || [] } ) )
+ .catch( err => next( err ) );
+} );
+
+app.get( "/metrics-list", function( req, res ) {
+ var metricsDir = "documents/ITDG/metrics";
+
+ // Ensure that the metrics directory exists, then get the list of files in it
+ mkdirp( metricsDir )
+ .then( made => fs.readdirAsync( metricsDir ) )
+ .then( files => res.send( { metricsFilenames: files || [] } ) )
+ .catch( err => next( err ) );
+} );
+
+
+app.get( "/export-scenarios", function( req, res ) {
+ const scenarioName = req.query.scenarioName;
+ const appDir = "documents/ITDG";
+ const stateBaseDir = appDir + "/" + scenarioName;
+ let inputFilePaths = [];
+ Promise.all( [
+
+ // Get the state files
+ fs.readdirAsync( stateBaseDir ).then( paths =>
+ inputFilePaths = inputFilePaths.concat( paths.map( path => stateBaseDir + "/" + path ) ) ),
+
+ // Get the list of files referenced in the scenario
+ fs.readFileAsync( stateBaseDir + "/saveState.vwf.json" )
+ .then( jsonBuffer => {
+ const filePathRegex = /"stream\/documents\/ITDG\/[^"]*"/g;
+ const dependencyFilePaths =
+ ( jsonBuffer.toString().match( filePathRegex ) || [] )
+ .map( match => match.slice( 1, -1 ).replace( "stream/", "" ) );
+ const fileExtensionRegex = /\.[^\.]*$/;
+ const tifDependencyFilePaths =
+ dependencyFilePaths
+ .map( filePath => filePath.replace( fileExtensionRegex, ".tif" ) )
+ .filter( tifPath => fs.existsSync( tifPath ) );
+ const tmsDependencyFilePaths =
+ dependencyFilePaths
+ .map( filePath => filePath.replace( fileExtensionRegex, ".tms" ) )
+ .filter( tmsPath => fs.existsSync( tmsPath ) );
+ const jpgDependencyFilePaths =
+ dependencyFilePaths
+ .map( filePath => filePath.replace( fileExtensionRegex, ".jpg" ) )
+ .filter( jpgPath => fs.existsSync( jpgPath ) );
+ const gifDependencyFilePaths =
+ dependencyFilePaths
+ .map( filePath => filePath.replace( fileExtensionRegex, ".gif" ) )
+ .filter( gifPath => fs.existsSync( gifPath ) );
+ const pngDependencyFilePaths =
+ dependencyFilePaths
+ .map( filePath => filePath.replace( fileExtensionRegex, ".png" ) )
+ .filter( pngPath => fs.existsSync( pngPath ) );
+ inputFilePaths = _.union(
+ inputFilePaths,
+ dependencyFilePaths,
+ tifDependencyFilePaths,
+ tmsDependencyFilePaths,
+ jpgDependencyFilePaths,
+ gifDependencyFilePaths,
+ pngDependencyFilePaths );
+ } )
+ ] ).then( () => {
+
+ // Get an array of the output files that will be created in the staging area
+ const tempDir = appDir + "/export/" + Math.random();
+ const stagingDir = tempDir + "/staging";
+ const outputFilePaths =
+ inputFilePaths.map( inputFilePath =>
+ inputFilePath.replace( "documents/ITDG", stagingDir ) );
+
+ // Determine the list of directories that need to be created
+ const zipDir = tempDir + "/output";
+ const zipFilename = scenarioName + ".zip";
+ const outputFileDirs = outputFilePaths.map( outputFilePath =>
+ outputFilePath.slice( 0, outputFilePath.lastIndexOf( "/" ) ) );
+ const dirsToCreate = [ stagingDir, zipDir ].concat( outputFileDirs );
+
+ // Determine the path for the output zip file
+ const zipFilePath = zipDir + "/" + zipFilename;
+
+ // Perform the export
+ Promise.all( dirsToCreate.map( dir => fs.mkdirpAsync( dir ) ) )
+ .then( () =>
+ Promise.all( inputFilePaths.map( ( inputFilePath, index ) =>
+ fs.copyAsync( inputFilePath, outputFilePaths[ index ] ) ) ) )
+ .then( () => zipFolder( stagingDir, zipFilePath ) )
+ .then( () =>
+ Promise.promisify( res.download ).call( res, zipFilePath ) )
+ .then( () => fs.removeAsync( tempDir ) );
+ } );
+} );
+
+app.post( "/scenarios", function( req, res, next ) {
+
+ var now = new Date();
+
+ var templateFilename =
+ "documents/ITDG/" + "scenarioTemplate" + "/saveState.vwf.json";
+
+ var scenarioName =
+ req.body.name.trim().replace( /[^0-9A-Za-z]+/g, "-" );
+ var scenarioTitle =
+ req.body.title.trim();
+ var scenarioFilename =
+ "documents/ITDG/" + scenarioName + "/saveState.vwf.json";
+ var scenarioFilename2 =
+ "documents/ITDG/" + scenarioName + "/saveState_" + now.valueOf() + ".vwf.json";
+
+ mkdirp( "documents/ITDG/" + scenarioName ).
+ then( () => fs.readFileAsync( templateFilename ) ).
+ then( json => JSON.parse( json ) ).
+ then( state => createScenario( state ) ).
+ tap( state => xapi.logCreation( state, "/ITDG", scenarioName, req.user ) ).
+ then( state => JSON.stringify( state ) ).
+ then( json => Promise.all( [ fs.writeFileAsync( scenarioFilename, json ), fs.writeFileAsync( scenarioFilename2, json ) ] ) ).
+ then( () => res.status( 201 ).send( { document: { uri: helpers.JoinPath( "/ITDG", helpers.GenerateInstanceID(), "load", scenarioName ) } } ) ).
+ catch( error => next( error ) );
+
+ fs.readFileAsync( templateFilename.replace( /vwf.json$/, "vwf.config.json" ) ).
+ then( json => Promise.all( [ fs.writeFileAsync( scenarioFilename.replace( /vwf.json$/, "vwf.config.json" ), json ), fs.writeFileAsync( scenarioFilename2.replace( /vwf.json$/, "vwf.config.json" ), json ) ] ) ).
+ catch( error => next( error ) );
+
+ /// Create a scenario from the template and request data.
+
+ function createScenario( state ) {
+
+ var patch = _.set( {}, "nodes[1].children.scenarioController.properties", {
+ scenarioName: scenarioName,
+ scenarioTitle: scenarioTitle,
+ } );
+
+ return _.merge( state, patch );
+ }
+
+} );
+
+app.post( "/sessions", function( req, res, next ) {
+
+ var now = new Date();
+
+ var scenarioName =
+ req.body.name.trim().replace( /[^0-9A-Za-z]+/g, "-" );
+ var scenarioFilename =
+ "documents/ITDG/" + scenarioName + "/saveState.vwf.json";
+
+ var sessionCompany =
+ req.body.company.trim().replace( /[^0-9A-Za-z]+/g, "-" );
+ var sessionPlatoon =
+ Number( req.body.platoon ).toString();
+ var sessionUnit =
+ Number( req.body.unit ).toString();
+
+ var sessionName =
+ "class_" + scenarioName +
+ "_Co" + sessionCompany +
+ "_Plt" + sessionPlatoon +
+ "_Unit" + sessionUnit +
+ "_" + now.getFullYear();
+
+ var sessionFilename =
+ "documents/ITDG/" + sessionName + "/saveState.vwf.json";
+ var sessionFilename2 =
+ "documents/ITDG/" + sessionName + "/saveState_" + now.valueOf() + ".vwf.json";
+
+ mkdirp( "documents/ITDG/" + sessionName ).
+ then( () => fs.readFileAsync( scenarioFilename ) ).
+ then( json => JSON.parse( json ) ).
+ then( state => createSession( state ) ).
+ tap( state => xapi.logCreation( state, "/ITDG", sessionName, req.user ) ).
+ then( state => JSON.stringify( state ) ).
+ then( json => Promise.all( [ fs.writeFileAsync( sessionFilename, json ), fs.writeFileAsync( sessionFilename2, json ) ] ) ).
+ then( () => res.status( 201 ).send( { document: { uri: helpers.JoinPath( "/ITDG", helpers.GenerateInstanceID(), "load", sessionName ) } } ) ).
+ catch( error => next( error ) );
+
+ fs.readFileAsync( scenarioFilename.replace( /vwf.json$/, "vwf.config.json" ) ).
+ then( json => Promise.all( [ fs.writeFileAsync( sessionFilename.replace( /vwf.json$/, "vwf.config.json" ), json ), fs.writeFileAsync( sessionFilename2.replace( /vwf.json$/, "vwf.config.json" ), json ) ] ) ).
+ catch( error => next( error ) );
+
+ /// Create a session from a scenario and request data.
+
+ function createSession( state ) {
+
+ var patch = _.set( {}, "nodes[1].children.scenarioController.properties", {
+ classroom: {
+ company: sessionCompany,
+ platoon: sessionPlatoon,
+ unit: sessionUnit,
+ },
+ dateOfClass:
+ now.toISOString(),
+ } );
+
+ return _.merge( state, patch );
+ }
+
+} );
+
+app.post( "/import-scenarios", upload.single( "file" ), function( req, res ) {
+ var file = req.file;
+ if ( file.mimetype === "application/x-zip-compressed" ) {
+ var zip = new admZip( file.path );
+ zip.extractAllTo( "documents/ITDG", true );
+ res.end();
+ fs.removeAsync( file.path );
+ } else {
+ res.status( 500 ).send( "File is not a .zip file: " + file.originalname );
+ }
+} );
+
+app.post( "/sim", function( req, res, next ) {
+ amqp.connect( "amqp://" + config.get( "txs.amqp_host" ) ).
+ then( connection => connection.createChannel() ).
+ tap( channel => channel.assertExchange( config.get( "txs.amqp_exchange" ), "topic", { durable: false } ) ).
+ tap( channel => channel.publish( config.get( "txs.amqp_exchange" ), config.get( "txs.amqp_channel" ), new Buffer( req.body.mission ) ) ).
+ then( () => res.sendStatus( 200 ) ).
+ catch( error => next( error ) );
+} );
+
+app.post( "/sendKML", function( req, res, next ) {
+ kmlHandler.sendKML( req, res, next );
+} );
+
+/// sessionSecret
+
+function sessionSecret() {
+ return config.get( "session.secret" ) ||
+ crypto.randomBytes( 32 ).toString( "hex" );
+}
diff --git a/lib/nodejs/manifest.js b/lib/nodejs/manifest.js
new file mode 100644
index 000000000..77bd8932d
--- /dev/null
+++ b/lib/nodejs/manifest.js
@@ -0,0 +1,521 @@
+var http = require( "http" ),
+ rp = require( "request-promise" ),
+ Promise = require( "bluebird" ),
+ glob = Promise.promisifyAll( require( "glob" ) ),
+ fs = Promise.promisifyAll( require( "fs" ) ),
+ _ = require( "lodash" );
+
+var reflector = require( "./reflector" ),
+ helpers = require( "./helpers" );
+
+/// Return a promise for the list of applications available to launch, instances to connect to, and
+/// documents to load.
+
+function manifest() {
+ return applications().then( documents ).then( instances );
+}
+
+/// {
+/// "/path/to/application.vwf": {},
+/// "/path/to/another/application.vwf": {},
+/// ...
+/// }
+
+function applications() {
+
+ var applications = {};
+
+ return glob.globAsync( "public/**/*.vwf.@(json|yaml)" ).map( function( path ) {
+
+ var applicationKey = path.replace( /^public/, "" ).replace( /\.(json|yaml)$/, "" );
+ applications[ applicationKey ] = applications[ applicationKey ] || {};
+
+ } ).then( function() {
+
+ return applications;
+
+ } );
+
+}
+
+/// {
+/// "/path/to/application.vwf": {
+/// name: {
+/// application:
+/// application,
+/// scenario: {
+/// state: { title },
+/// document: { uri, timestamp },
+/// instance,
+/// },
+/// sessions: [
+/// {
+/// state: { classroom: { company, platoon, unit } },
+/// document: { uri, timestamp },
+/// instance,
+/// },
+/// ...
+/// ],
+/// },
+/// ...
+/// },
+/// ...
+/// }
+
+function documents( applications ) {
+
+ return glob.globAsync( "documents/**/saveState?(_+([0-9])).vwf.json" ).map( function( path ) {
+
+ var match = path.match( RegExp( "documents/(.+)/([^/]+)/saveState(?:_([0-9]+))?.vwf.json" ) );
+
+ if ( match ) {
+
+ var applicationKey = "/" + match[1] + "/index.vwf";
+ var name = match[2];
+ var revision = match[3];
+
+ if ( ! revision ) {
+
+ applications[ applicationKey ] = applications[ applicationKey ] || {};
+
+ return Promise.join( fs.readFileAsync( path ), fs.statAsync( path ), function( json, stats ) {
+
+ var state = JSON.parse( json );
+
+ var applicationNode = ( ( state || {} ).nodes || [] ).filter( function( node ) {
+ return node.patches === applicationKey;
+ } )[ 0 ];
+
+ // scenarioName:
+ // "name"
+ // scenarioTitle:
+ // "longer title"
+ // classroom: {
+ // company: "Company",
+ // platoon: "1",
+ // unit: "1" },
+ // dateOfClass:
+ // "1970-01-01T00:00:00Z"
+
+ var scenarioProperties = ( ( ( applicationNode || {} ).children || {} ).scenarioController || {} ).properties || {};
+
+ var scenarioKey = scenarioProperties.scenarioName;
+ if (scenarioKey) {
+ var sName = scenarioKey.match( RegExp( "class_(.*)_Co" ) );
+ if (sName && sName[1]){
+ scenarioKey = sName[1];
+ }
+ }
+
+ var sessionKey = _.pick( scenarioProperties.classroom || {}, [ "company", "platoon", "unit" ] );
+
+ if ( _.keys( sessionKey ).length !== 3 ) {
+ sessionKey = undefined;
+ }
+
+ var participants = ( ( ( applicationNode || {} ).children || {} ).participants || {} ).children || {};
+
+ var participantInstructors = Object.keys( participants ).map( function( participantName ) {
+ return !! ( participants[ participantName ].properties || {} ).isInstructor;
+ } );
+
+ var participantCounts = {
+ document: {
+ instructors: participantInstructors.filter( function( isInstructor ) {
+ return isInstructor } ).length,
+ students: participantInstructors.filter( function( isInstructor ) {
+ return ! isInstructor } ).length,
+ }
+ };
+
+ if ( scenarioKey ) {
+
+ var document = {
+ uri: applicationKey.replace( /\/index.vwf$/, "" ) + "/" + helpers.GenerateInstanceID() + "/load/" + name + "/",
+ timestamp: scenarioProperties.scenarioTimestamp ? +new Date( scenarioProperties.scenarioTimestamp ) : stats.mtime
+ };
+
+ updateScenario( applications, applicationKey, scenarioKey, sessionKey, scenarioProperties,
+ participantCounts, document );
+
+ }
+
+ } );
+
+ }
+
+ }
+
+ } ).then( function() {
+
+ return applications;
+
+ } );
+
+}
+
+/// {
+/// "/path/to/application.vwf": {
+/// name: {
+/// application:
+/// application,
+/// scenario: {
+/// state: { title },
+/// document: { uri, timestamp },
+/// instance,
+/// },
+/// sessions: [
+/// {
+/// state: { classroom: { company, platoon, unit } },
+/// document: { uri, timestamp },
+/// instance,
+/// },
+/// ...
+/// ],
+/// },
+/// ...
+/// },
+/// ...
+/// }
+
+function instances( applications ) {
+
+ return Promise.map( Object.keys( applications ), function( applicationKey ) {
+
+ var regexp = new RegExp( "^" + _.escapeRegExp( applicationKey ) + "(/|$)" );
+
+ var applicationInstanceIDs = Object.keys( reflector.GetInstances() ).filter( function( instanceID ) {
+ return instanceID.search( regexp ) === 0;
+ } );
+
+ return Promise.map( applicationInstanceIDs, function( instanceID ) {
+
+ var scenarioPromise = reflector.Evaluate( instanceID, applicationKey,
+ "this.scenarioController && this.scenarioController.properties" );
+
+ var participantsPromise = reflector.Evaluate( instanceID, "http://vwf.example.com/clients.vwf",
+ "this.children.map( function( client ) { return !! client.instructor } )" );
+
+ return Promise.join( scenarioPromise, participantsPromise, function( scenarioProperties, participantInstructors ) {
+
+ var scenarioProperties = scenarioProperties || {};
+ scenarioProperties.classroom = scenarioProperties.classroom || {};
+
+ var scenarioKey = scenarioProperties.scenarioName;
+ if (scenarioKey) {
+ var sName = scenarioKey.match( RegExp( "class_(.*)_Co" ) );
+ if (sName && sName[1]){
+ scenarioKey = sName[1];
+ }
+ }
+
+ var sessionKey = _.pick( scenarioProperties.classroom, [ "company", "platoon", "unit" ] );
+
+ if ( _.keys( sessionKey ).length !== 3 ) {
+ sessionKey = undefined;
+ }
+
+ var participantCounts = {
+ instance: {
+ instructors: participantInstructors.filter( function( isInstructor ) {
+ return isInstructor } ).length,
+ students: participantInstructors.filter( function( isInstructor ) {
+ return ! isInstructor } ).length,
+ isReview: reflector.IsReview( instanceID )
+ }
+ };
+
+ if ( scenarioKey ) {
+
+ var instance = instanceID.replace( /\/index.vwf$/, "" ) + "/";
+
+ updateScenario( applications, applicationKey, scenarioKey, sessionKey, scenarioProperties,
+ participantCounts, undefined, instance );
+ }
+
+ } ).catch( function( exception ) {
+
+ // Catch `refector.Evaluate` timeouts. Instances that can't be read are omitted from the
+ // manifest since `scenarioProperties` is required to identify them.
+
+ } );
+
+ } );
+
+ } ).then( function() {
+
+ return applications;
+
+ } );
+
+}
+
+/// Update a scenario in the application database with new a document and/or instance of the
+/// scenario, or a scenario session.
+///
+/// @param applications
+/// Application, scenario, session database.
+/// @param applicationKey
+/// Application id for the document/instance.
+/// @param scenarioKey
+/// Scenario id for the document/instance. The document/instance is the scenario if `sessionKey`
+/// is falsy.
+/// @param [sessionKey]
+/// Session id of the document/instance if it is a session.
+/// @param scenarioProperties
+/// Important properties from the saved document or live instance.
+/// @param [document]
+/// URL and timestamp of a scenario/session saved document.
+/// @param [instance]
+/// URL of a scenario/session live instance.
+
+function updateScenario( applications, applicationKey, scenarioKey, sessionKey, scenarioProperties,
+ participantCounts, document, instance ) {
+
+ /// Locate the application record in the database.
+
+ var application = applications[ applicationKey ];
+
+ /// Locate or create the scenario record in the application.
+
+ var scenario = application[ scenarioKey ] = application[ scenarioKey ] || {
+ application:
+ applicationKey,
+ scenario: {
+ state: {} },
+ sessions: [],
+ };
+
+ if ( ! sessionKey ) {
+
+ // This is a scenario. Record the document/instance state and the document and/or instance URLs.
+
+ _.merge( scenario.scenario, {
+ state: scenarioProperties,
+ completion: participantCounts,
+ document: document,
+ instance: instance,
+ } );
+
+ } else {
+
+ // This is a session. Update the session record if it exists, or create a new session record.
+
+ var matchedSession = scenario.sessions.reduce( function( matchedSession, session ) {
+
+ // If the document/instance matches an existing session, record the document and/or instance
+ // URLs in the session record.
+
+ if ( _.isEqual( session.state.classroom, sessionKey ) ) {
+ _.merge( session, {
+ completion: participantCounts,
+ document: document,
+ instance: instance,
+ } );
+ return true;
+ } else {
+ return matchedSession;
+ }
+
+ }, false );
+
+ // If there is no matching session, record the document/instance state and the document and/or
+ // instance URLs in a new session record.
+
+ if ( ! matchedSession ) {
+
+ var session = {
+ state: {},
+ };
+
+ _.merge( session, {
+ state: scenarioProperties,
+ completion: participantCounts,
+ document: document,
+ instance: instance,
+ } );
+
+ scenario.sessions.push( session );
+
+ }
+
+ }
+
+}
+
+/// Return a sample list of applications available to launch, instances to connect to, and documents
+/// to load.
+
+function sample() {
+
+ return {
+
+ "/TDG/index.vwf": {
+
+ "scenario-with-document-without-instance": {
+ application:
+ "/TDG/index.vwf",
+ scenario: {
+ document: {
+ uri: "/TDG/load/scenario-with-document-without-instance",
+ timestamp: +new Date( "2015-01-01T01:00:00-0500" ) },
+ state: {
+ title: "Scenario with document without instance" },
+ },
+ },
+
+ "scenario-without-document-with-instance": {
+ application:
+ "/TDG/index.vwf",
+ scenario: {
+ instance:
+ "/TDG/0123456789ABCDEF",
+ state: {
+ title: "Scenario without document with instance" },
+ },
+ },
+
+ "scenario-with-document-with-instance": {
+ application:
+ "/TDG/index.vwf",
+ scenario: {
+ document: {
+ uri: "/TDG/load/scenario-with-document-with-instance",
+ timestamp: +new Date( "2015-01-01T03:00:00-0500" ) },
+ instance:
+ "/TDG/0123456789ABCDEF",
+ state: {
+ title: "Scenario with document with instance" },
+ },
+ },
+
+ "scenario-without-sessions": {
+ application:
+ "/TDG/index.vwf",
+ scenario: {
+ document: {
+ uri: "/TDG/load/scenario-without-sessions",
+ timestamp: +new Date( "2015-01-01T04:00:00-0500" ) },
+ state: {
+ title: "Scenario without sessions" },
+ },
+ },
+
+ "scenario-with-session-with-document-without-instance": {
+ application:
+ "/TDG/index.vwf",
+ scenario: {
+ document: {
+ uri: "/TDG/load/scenario-with-session-with-document-without-instance",
+ timestamp: +new Date( "2015-01-01T05:00:00-0500" ) },
+ state: {
+ title: "Scenario with session with document without instance" },
+ },
+ sessions: [ {
+ document: {
+ uri: "/TDG/load/scenario-with-session-with-document-without-instance+session1",
+ timestamp: +new Date( "2015-02-01T00:00:00-0500" ) },
+ state: {
+ classroom: { company: "Company", platoon: "1", unit: "1" },
+ }
+ } ]
+ },
+
+ "scenario-with-session-without-document-with-instance": {
+ application:
+ "/TDG/index.vwf",
+ scenario: {
+ document: {
+ uri: "/TDG/load/scenario-with-session-without-document-with-instance",
+ timestamp: +new Date( "2015-01-01T06:00:00-0500" ) },
+ state: {
+ title: "Scenario with session without document with instance" },
+ },
+ sessions: [ {
+ instance:
+ "/TDG/0123456789ABCDEF",
+ state: {
+ classroom: { company: "Company", platoon: "1", unit: "1" },
+ }
+ } ]
+ },
+
+ "scenario-with-session-with-document-with-instance": {
+ application:
+ "/TDG/index.vwf",
+ scenario: {
+ document: {
+ uri: "/TDG/load/scenario-with-session-with-document-with-instance",
+ timestamp: +new Date( "2015-01-01T07:00:00-0500" ) },
+ state: {
+ title: "Scenario with session with document with instance" },
+ },
+ sessions: [ {
+ document: {
+ uri: "/TDG/load/scenario-with-session-with-document-with-instance+session1",
+ timestamp: +new Date( "2015-02-01T00:00:00-0500" ) },
+ instance:
+ "/TDG/0123456789ABCDEF",
+ state: {
+ classroom: { company: "Company", platoon: "1", unit: "1" },
+ }
+ } ]
+ },
+
+ "scenario-with-multiple-sessions": {
+ application:
+ "/TDG/index.vwf",
+ scenario: {
+ document: {
+ uri: "/TDG/load/scenario-with-multiple-sessions",
+ timestamp: +new Date( "2015-01-01T08:00:00-0500" ) },
+ state: {
+ title: "Scenario with multiple sessions" },
+ },
+ sessions: [ {
+ document: {
+ uri: "/TDG/load/scenario-with-multiple-sessions+session1",
+ timestamp: +new Date( "2015-02-01T00:00:00-0500" ) },
+ instance:
+ "/TDG/0123456789ABCDEF",
+ state: {
+ classroom: { company: "Company", platoon: "1", unit: "1" },
+ }
+ }, {
+ document: {
+ uri: "/TDG/load/scenario-with-multiple-sessions+session2",
+ timestamp: +new Date( "2015-02-02T00:00:00-0500" ) },
+ instance:
+ "/TDG/0123456789ABCDEF",
+ state: {
+ classroom: { company: "Company", platoon: "1", unit: "1" },
+ }
+ }, {
+ document: {
+ uri: "/TDG/load/scenario-with-multiple-sessions+session3",
+ timestamp: +new Date( "2015-02-03T00:00:00-0500" ) },
+ instance:
+ "/TDG/0123456789ABCDEF",
+ state: {
+ classroom: { company: "Company", platoon: "1", unit: "1" },
+ }
+ }, {
+ document: {
+ uri: "/TDG/load/scenario-with-multiple-sessions+session4",
+ timestamp: +new Date( "2015-02-04T00:00:00-0500" ) },
+ instance:
+ "/TDG/0123456789ABCDEF",
+ state: {
+ classroom: { company: "Company", platoon: "1", unit: "1" },
+ }
+ } ]
+ }
+
+ }
+
+ };
+
+}
+
+module.exports = manifest;
+module.exports.sample = sample;
diff --git a/lib/nodejs/parse-url.js b/lib/nodejs/parse-url.js
index 9e1a2b909..bfa7a42ae 100755
--- a/lib/nodejs/parse-url.js
+++ b/lib/nodejs/parse-url.js
@@ -78,9 +78,11 @@ function Process( updatedURL ) {
if ( segments.length > 0 ) {
result[ 'private_path' ] = segments.join("/");
}
+ return result;
+ } else {
+ return undefined;
}
- return result;
}
exports.Process = Process;
\ No newline at end of file
diff --git a/lib/nodejs/persistence.js b/lib/nodejs/persistence.js
index 9d1814deb..bd650b631 100755
--- a/lib/nodejs/persistence.js
+++ b/lib/nodejs/persistence.js
@@ -33,8 +33,13 @@ function LoadSaveObject( loadInfo ) {
if ( loadInfo[ 'save_name' ] ) {
var fileName = helpers.JoinPath( "./documents", loadInfo[ 'application_path' ], loadInfo[ 'save_name' ], "saveState_" + loadInfo[ 'save_revision' ] + ".vwf.json" );
if ( helpers.IsFile( fileName ) ) {
- var fileContents = fs.readFileSync( fileName, "utf8" );
- return JSON.parse( fileContents );
+ try {
+ var fileContents = fs.readFileSync( fileName, "utf8" );
+ return JSON.parse( fileContents );
+ } catch( exception ) {
+ console.log( "Error reading", fileName + ".", exception.toString() );
+ return undefined;
+ }
}
}
return undefined;
@@ -249,27 +254,30 @@ function HandlePersistenceSave( request, response, parsedRequest, segments ) {
CreateSaveDirectory( parsedRequest[ 'public_path' ], saveName, saveRevision );
var persistenceData = "";
var persistenceLength = 0;
- request.on( 'data', function ( data ) {
- persistenceData += data;
- if ( persistenceData.length > 50000000 ) {
- persistenceData = "";
- response.writeHead( 413, { 'Content-Type': 'text/plain' } ).end( );
- request.connection.destroy();
- }
- } );
- request.on( 'end', function( ) {
- if ( persistenceData.length > 0 ) {
- var processedPost = querystring.parse( persistenceData );
- fs.writeFileSync( helpers.JoinPath( './documents', parsedRequest[ 'public_path' ], saveName, 'saveState' + processedPost[ "extension" ] ), processedPost[ "jsonState" ] );
- fs.writeFileSync( helpers.JoinPath( './documents', parsedRequest[ 'public_path' ], saveName, 'saveState_' + saveRevision + processedPost[ "extension" ] ), processedPost[ "jsonState" ] );
- }
- } );
+ if ( typeof request.body === "object" ) {
+ fs.writeFileSync( helpers.JoinPath( './documents', parsedRequest[ 'public_path' ], saveName, 'saveState' + request.body[ "extension" ] ), request.body[ "jsonState" ] );
+ fs.writeFileSync( helpers.JoinPath( './documents', parsedRequest[ 'public_path' ], saveName, 'saveState_' + saveRevision + request.body[ "extension" ] ), request.body[ "jsonState" ] );
+ response.writeHead( 200 );
+ response.end();
+ } else {
+ request.on( 'data', function ( data ) {
+ persistenceData += data;
+ } );
+ request.on( 'end', function( ) {
+ if ( persistenceData.length > 0 ) {
+ var processedPost = querystring.parse( persistenceData );
+ fs.writeFileSync( helpers.JoinPath( './documents', parsedRequest[ 'public_path' ], saveName, 'saveState' + processedPost[ "extension" ] ), processedPost[ "jsonState" ] );
+ fs.writeFileSync( helpers.JoinPath( './documents', parsedRequest[ 'public_path' ], saveName, 'saveState_' + saveRevision + processedPost[ "extension" ] ), processedPost[ "jsonState" ] );
+ response.writeHead( 200 );
+ response.end();
+ }
+ } );
+ }
return true;
}
return false;
}
-
// The Serve function takes the nodeJS request, nodeJS response and the parsedRequest, and
// attempts to see if it is a properly formed persistence related request.
// If so, it serves the request, and returns true.
@@ -309,6 +317,7 @@ function Serve( request, response, parsedRequest ) {
if ( ( request.method == "POST" ) && ( segments.length > 1 ) ) {
return HandlePersistenceSave( request, response, parsedRequest, segments );
}
+ return false;
}
}
}
diff --git a/lib/nodejs/reflector.js b/lib/nodejs/reflector.js
index 97f539ce8..9f91fce20 100755
--- a/lib/nodejs/reflector.js
+++ b/lib/nodejs/reflector.js
@@ -3,12 +3,20 @@
var parseurl = require( './parse-url' ),
persistence = require( './persistence' ),
+ xapi = require( "./xapi" ),
helpers = require( './helpers' ),
- fs = require( 'fs' );
+ fs = require( 'fs' ),
+ cookieParser = require( 'cookie-parser' ),
+ cookieSession = require( 'cookie-session' ),
+ config = require( 'config' );
function parseSocketUrl( socket ) {
- var application = socket.handshake.url.substring(0, socket.handshake.url.indexOf("/1/?t="));
- return parseurl.Process( application );
+ const handshake = socket.handshake;
+ const url = handshake.url;
+ const application = url.substring( 0, url.indexOf( "/1/?t=" ) );
+ const processedURL = parseurl.Process( application );
+ processedURL.isReview = ( handshake.headers.referer || "" ).endsWith( "isReview=true" );
+ return processedURL;
}
function GetLoadForSocket( processedURL ) {
@@ -48,9 +56,11 @@ function OnConnection( socket ) {
if( !global.instances[ namespace ] ) {
global.instances[ namespace ] = { };
global.instances[ namespace ].clients = { };
+ global.instances[ namespace ].pendingList = [ ];
global.instances[ namespace ].start_time = undefined;
global.instances[ namespace ].pause_time = undefined;
global.instances[ namespace ].rate = 1.0;
+ global.instances[ namespace ].isReview = processedURL.isReview;
global.instances[ namespace ].setTime = function( time ) {
this.start_time = GetNow( ) - time;
this.pause_time = undefined;
@@ -105,6 +115,7 @@ function OnConnection( socket ) {
}
};
global.instances[ namespace ].setTime( 0.0 );
+
if ( saveObject ) {
if ( saveObject[ "queue" ] ) {
if ( saveObject[ "queue" ][ "time" ] ) {
@@ -159,11 +170,17 @@ function OnConnection( socket ) {
//keep track of the timer for this instance
global.instances[ namespace ].timerID = setInterval( function ( ) {
+ var message = { parameters: [ ], time: global.instances[ namespace ].getTime( ) };
for ( var i in global.instances[ namespace ].clients ) {
var client = global.instances[ namespace ].clients[ i ];
- client.emit( 'message', { parameters: [ ], time: global.instances[ namespace ].getTime( ) } );
+ if ( ! client.pending ) {
+ client.emit( 'message', message );
+ }
}
- }, 50 );
+ if ( global.instances[ namespace ].pendingList.pending ) {
+ global.instances[ namespace ].pendingList.push( message );
+ }
+ }, 100 );
}
@@ -171,19 +188,22 @@ function OnConnection( socket ) {
global.instances[ namespace ].clients[ socket.id ] = socket;
socket.pending = true;
- socket.pendingList = [ ];
- //Create a child in the application's 'clients.vwf' global to represent this client.
- var clientMessage = { action: "createChild", parameters: [ "http://vwf.example.com/clients.vwf", socket.id, {} ], time: global.instances[ namespace ].getTime( ) };
+ //Get the descriptor for the `clients.vwf` child.
+ var clientDescriptor = GetClientDescriptor( socket );
// The time for the setState message should be the time the new client joins, so save that time
var setStateTime = global.instances[ namespace ].getTime( );
- //The client is the first, is can just load the application, and mark it not pending
+ // If this client is the first, it can just load the application, and mark it not pending
if ( Object.keys( global.instances[ namespace ].clients ).length === 1 ) {
if ( saveObject ) {
- socket.emit( 'message', { action: "setState", parameters: [saveObject], time: global.instances[ namespace ].getTime( ) } );
+ socket.emit( 'message', {
+ action: "setState",
+ parameters: [ saveObject ],
+ time: global.instances[ namespace ].getTime( )
+ } );
}
else {
var instance = namespace;
@@ -204,26 +224,46 @@ function OnConnection( socket ) {
],
time: global.instances[ namespace ].getTime( )
} );
-
- socket.emit( 'message', clientMessage );
-
}
+
socket.pending = false;
+
+ xapi.logClient( saveObject, loadInfo[ 'application_path' ], loadInfo[ 'save_name' ], namespace, clientDescriptor.properties || {}, true, true );
}
else { //this client is not the first, we need to get the state and mark it pending
- var firstclient = Object.keys( global.instances[ namespace ].clients )[ 0 ];
- firstclient = global.instances[ namespace ].clients[ firstclient ];
- firstclient.emit( 'message', { action: "getState", respond: true, time: global.instances[ namespace ].getTime( ) } );
- global.instances[ namespace ].Log( 'GetState from Client', 2 );
+ if ( ! global.instances[ namespace ].pendingList.pending ) {
+ var firstclient = Object.keys( global.instances[ namespace ].clients )[ 0 ];
+ firstclient = global.instances[ namespace ].clients[ firstclient ];
+ firstclient.emit( 'message', {
+ action: "getState",
+ respond: true,
+ time: global.instances[ namespace ].getTime( )
+ } );
+ global.instances[ namespace ].Log( 'GetState from Client', 2 );
+ global.instances[ namespace ].pendingList.pending = true;
+ }
socket.pending = true;
- socket.pendingList.push( clientMessage );
- for ( var i in global.instances[ namespace ].clients ) {
- var client = global.instances[ namespace ].clients[ i ];
- if( client.id != socket.id ) {
- client.emit ( 'message', clientMessage );
- }
+
+ }
+
+ //Create a child in the application's 'clients.vwf' global to represent this client.
+ var clientNodeMessage = {
+ action: "createChild",
+ parameters: [ "http://vwf.example.com/clients.vwf", socket.id, clientDescriptor ],
+ time: global.instances[ namespace ].getTime( )
+ };
+
+ // Send messages to all the existing clients (that are not pending),
+ // telling them to create a new node under the "clients" parent for the new client
+ for ( var i in global.instances[ namespace ].clients ) {
+ var client = global.instances[ namespace ].clients[ i ];
+ if ( !client.pending ) {
+ client.emit ( 'message', clientNodeMessage );
}
}
+ if ( global.instances[ namespace ].pendingList.pending ) {
+ global.instances[ namespace ].pendingList.push( clientNodeMessage );
+ }
socket.on( 'message', function ( msg ) {
@@ -232,62 +272,179 @@ function OnConnection( socket ) {
var message = JSON.parse( msg );
}
catch ( e ) {
+ console.error( "Error on socket message: ", e );
return;
}
message.client = socket.id;
message.time = global.instances[ namespace ].getTime( );
- //distribute message to all clients on given instance
- for ( var i in global.instances[ namespace ].clients ) {
- var client = global.instances[ namespace ].clients[ i ];
+ if ( message.result === undefined ) {
+
+ //distribute message to all clients on given instance
+ for ( var i in global.instances[ namespace ].clients ) {
+ var client = global.instances[ namespace ].clients[ i ];
- //if the message was get state, then fire all the pending messages after firing the setState
- if ( message.action == "getState" ) {
- global.instances[ namespace ].Log( 'Got State', 2 );
- var state = message.result;
- global.instances[ namespace ].Log( state, 2 );
- client.emit( 'message', { action: "setState", parameters: [ state ], time: setStateTime } );
- client.pending = false;
- for ( var j = 0; j < client.pendingList.length; j++ ) {
- client.emit( 'message', client.pendingList[ j ] );
- }
- client.pendingList = [ ];
- }
- else {
//just a regular message, so push if the client is pending a load, otherwise just send it.
- if ( client.pending === true ) {
- client.pendingList.push( message );
- }
- else {
+ if ( ! client.pending ) {
client.emit( 'message', message );
}
}
+
+ if ( global.instances[ namespace ].pendingList.pending ) {
+ global.instances[ namespace ].pendingList.push( message );
+ }
+
+ } else if ( message.action == "getState" ) {
+
+ //distribute message to all clients on given instance
+ for ( var i in global.instances[ namespace ].clients ) {
+ var client = global.instances[ namespace ].clients[ i ];
+
+ //if the message was get state, then fire all the pending messages after firing the setState
+ if ( client.pending ) {
+ global.instances[ namespace ].Log( 'Got State', 2 );
+ var state = message.result;
+ global.instances[ namespace ].Log( state, 2 );
+ client.emit( 'message', { action: "setState", parameters: [ state ], time: setStateTime } );
+ client.pending = false;
+ for ( var j = 0; j < global.instances[ namespace ].pendingList.length; j++ ) {
+ client.emit( 'message', global.instances[ namespace ].pendingList[ j ] );
+ }
+ xapi.logClient( state, undefined, undefined, namespace, GetClientDescriptor( client ).properties || {}, true, false );
+ }
+ }
+
+ global.instances[ namespace ].pendingList = [ ];
+
+ } else if ( message.action === "execute" ) {
+
+ var evaluation = socket.pendingEvaluations && socket.pendingEvaluations.shift();
+
+ if ( evaluation ) {
+ evaluation.resolve( message.result );
+ clearTimeout( evaluation.timeout );
+ }
+
}
+
} );
// When a client disconnects, go ahead and remove the instance data
socket.on( 'disconnect', function ( ) {
// Remove the disconnecting client
+ var leavingClient = global.instances[ namespace ].clients[ socket.id ];
global.instances[ namespace ].clients[ socket.id ] = null;
delete global.instances[ namespace ].clients[ socket.id ];
-
+ if ( leavingClient.pendingEvaluations ) {
+ leavingClient.pendingEvaluations.forEach( function( evaluation ) {
+ evaluation.reject( new Error( "connection closed" ) );
+ clearTimeout( evaluation.timeout );
+ } );
+ }
+
// Notify others of the disconnecting client. Delete the child representing this client in the application's `clients.vwf` global.
var clientMessage = { action: "deleteChild", parameters: [ "http://vwf.example.com/clients.vwf", socket.id ], time: global.instances[ namespace ].getTime( ) };
for ( var i in global.instances[ namespace ].clients ) {
var client = global.instances[ namespace ].clients[ i ];
- if( client.id != socket.id ) {
- client.emit ( 'message', clientMessage );
+ if ( ! client.pending ) {
+ client.emit ( 'message', clientMessage );
}
}
+ if ( global.instances[ namespace ].pendingList.pending ) {
+ global.instances[ namespace ].pendingList.push( clientMessage );
+ }
// If it's the last client, delete the data and the timer
if ( Object.keys( global.instances[ namespace ].clients ).length == 0 ) {
clearInterval( global.instances[ namespace ].timerID );
delete global.instances[ namespace ];
+ xapi.logClient( undefined, loadInfo[ 'application_path' ], loadInfo[ 'save_name' ], namespace, clientDescriptor.properties || {}, false, true );
+ } else {
+ xapi.logClient( undefined, loadInfo[ 'application_path' ], loadInfo[ 'save_name' ], namespace, clientDescriptor.properties || {}, false, false );
}
} );
}
-exports.OnConnection = OnConnection;
\ No newline at end of file
+function Evaluate( namespace, node, expression ) {
+
+ return new Promise( function( resolve, reject ) {
+
+ var firstClientID = Object.keys( global.instances[ namespace ].clients )[ 0 ];
+ var firstClient = global.instances[ namespace ].clients[ firstClientID ];
+
+ if ( firstClient ) {
+ firstClient.pendingEvaluations = firstClient.pendingEvaluations || [];
+ firstClient.pendingEvaluations.push( {
+ resolve: resolve,
+ reject: reject,
+ timeout: setTimeout( function() { reject( new Error( "timeout" ) ) }, 1000 ),
+ } );
+ firstClient.emit( "message", { node: node, action: "execute", parameters: [ expression ], respond: true, time: global.instances[ namespace ].getTime() } );
+ } else {
+ reject( new Error( "no clients are connected" ) );
+ }
+
+ } );
+
+}
+
+/// Get a descriptor for the `clients.vwf` child for a new client. An authenticator may set a
+/// descriptor in the session at `session.vwf.client`. If the authenticator doesn't provide a
+/// descriptor, use an empty node inheriting from `client.vwf`.
+
+function GetClientDescriptor( socket ) {
+
+ // socket.io doesn't provide access to the request and the session, but we do have the cookies.
+ // Create a mock request and run it through the session middleware to recreate the session. This
+ // creates a session object at `mockRequest.session`.
+
+ var mockRequest = {
+ headers: { cookie: socket.handshake.headers.cookie },
+ connection: {},
+ session: {},
+ };
+
+ var mockResponse = {
+ getHeader: function() {},
+ setHeader: function() {},
+ };
+
+ sessionStack.forEach( function( middleware ) {
+ middleware( mockRequest, mockResponse, function() {} );
+ } );
+
+ // Get the descriptor from `vwf.client` in the session.
+
+ var descriptor = ( mockRequest.session.vwf || {} ).client || {};
+
+ // Set the default prototype.
+
+ if ( ! descriptor.extends ) {
+ descriptor.extends = "http://vwf.example.com/client.vwf";
+ }
+
+ return descriptor;
+
+}
+
+/// Middleware stack to parse a cookie session from `req.headers.cookie` into `req.session`.
+
+var sessionStack = [
+ cookieParser(),
+ cookieSession( { secret: config.get( 'session.secret' ) } ),
+];
+
+function GetInstances() {
+ return global.instances;
+}
+
+function IsReview( instanceID ) {
+ return !!global.instances[ instanceID ].isReview;
+}
+
+exports.OnConnection = OnConnection;
+exports.Evaluate = Evaluate;
+exports.GetInstances = GetInstances;
+exports.IsReview = IsReview;
diff --git a/lib/nodejs/serve-handler.js b/lib/nodejs/serve-handler.js
index e3834def6..b98e7c822 100755
--- a/lib/nodejs/serve-handler.js
+++ b/lib/nodejs/serve-handler.js
@@ -18,7 +18,7 @@ function File( request, response, filename ) {
function Component( request, response, filename ) {
if ( request.method == "GET" ) {
if ( helpers.IsFile( filename + ".yaml" ) ) {
- serve.YAML( filename.replace( /\//g, libpath.sep ) + ".yaml", response, url.parse( request.url, true ) );
+ serve.YAML( request, filename.replace( /\//g, libpath.sep ) + ".yaml", response, url.parse( request.url, true ) );
return true;
}
if ( helpers.IsFile ( filename + ".json" ) ) {
diff --git a/lib/nodejs/serve.js b/lib/nodejs/serve.js
index 5413ac069..83aa69fc0 100755
--- a/lib/nodejs/serve.js
+++ b/lib/nodejs/serve.js
@@ -4,15 +4,19 @@
var filecache = require( './file-cache' ),
- YAML = require( 'js-yaml' ),
+ yamlcache = require( './yaml-cache' ),
fs = require( 'fs' ),
helpers = require( './helpers' );
-// First, if the FileCache has not been instantiated, do so.
+// First, if the FileCache and YamlCache have not been instantiated, do so.
if ( global.FileCache == undefined ) {
var FileCache = new filecache._FileCache( );
global.FileCache = FileCache;
}
+if ( global.YamlCache == undefined ) {
+ var YamlCache = new yamlcache._YamlCache( );
+ global.YamlCache = YamlCache;
+}
// Basic helper function to redirect a request.
function ServeRedirect( url, response ) {
@@ -78,46 +82,9 @@ function ServeJSONFile( filename, response, URL ) {
} );
}
-//Parse and serve a YAML file
-function ServeYAML( filename, response, URL ) {
- var tf = filename;
- fs.readFile( filename, "utf8", function ( err, data ) {
- if (err) {
- response.writeHead( 500, {
- "Content-Type": "text/plain"
- } );
- response.write( err + "\n" );
- response.end();
- return;
- }
-
- // Remove the Byte Order Mark (BOM) if one exists
- var file = data.replace( /^\uFEFF/, '' );
-
- //global.log(tf);
- try {
- var deYAML = JSON.stringify( YAML.load( file ) );
- } catch ( e ) {
- global.log( "error parsing YAML " + filename );
- _404( response );
- return;
- }
-
- var type = "application/json";
-
- var callback = URL.query.callback;
-
- if ( callback ) {
- deYAML = callback + "(" + deYAML + ")";
- type = "application/javascript";
- }
-
- response.writeHead( 200, {
- "Content-Type": type
- } );
- response.write( deYAML, "utf8" );
- response.end();
- } );
+// Serve YAML, using the YamlCache implementation.
+function ServeYAML( request, filename, response, URL ) {
+ YamlCache.ServeYAML( request, filename, response, URL );
}
diff --git a/lib/nodejs/stream.js b/lib/nodejs/stream.js
new file mode 100644
index 000000000..6659a930f
--- /dev/null
+++ b/lib/nodejs/stream.js
@@ -0,0 +1,148 @@
+var path = require( 'path' ),
+ mime = require( 'mime' ),
+ fs = require( 'fs' ),
+ helpers = require( './helpers' ),
+ util = require('util'),
+ formidable = require('formidable'),
+ os = require('os'),
+ parseurl = require( './parse-url' ),
+ AdmZip = require( 'adm-zip' );
+var streamStr = 'stream', uploadStr = 'upload';
+
+function OnStreamRequest( request, response, parsedRequest, segments ) {
+ if ( request.url !== '' && segments.length > 1 ) {
+ var file = path.join(__dirname, '../..');
+ for (var i = 1; i < segments.length; i++) {
+ file = path.join( file, segments[i] );
+ }
+ var m = mime.lookup( file );
+ if ( m.substring( 0, 5 ) === "image" ) {
+ fs.readFile( file, ( err, img ) => {
+ if ( err ) {
+ if ( err.code === "ENOENT" ) {
+ response.status( 404 ).send( file + " not found" );
+ } else {
+ throw err;
+ }
+ } else {
+ response.writeHead( 200, { 'Content-Type': m } );
+ response.end( img, 'binary' );
+ }
+ } );
+ } else {
+ fs.stat( file, ( err, stats ) => {
+ if ( err ) {
+ response.sendStatus( 404 );
+ return;
+ }
+
+ const total = stats.size;
+ if ( total > 0 ) {
+ const range = request.headers.range || "bytes=0-";
+ const positions = range.replace( /bytes=/, "" ).split( "-" );
+ const start = parseInt( positions[ 0 ], 10 );
+ const end = positions[1] ? parseInt( positions[1], 10 ) : total - 1;
+ response.writeHead( 206, {
+ "Content-Range": "bytes " + start + "-" + end + "/" + total,
+ "Accept-Ranges": "bytes",
+ "Content-Length": end - start + 1,
+ "Content-Type": m
+ } );
+ const stream =
+ fs.createReadStream( file, { start: start, end: end } ).
+ on( "open", () => stream.pipe( response ) ).
+ on( "error", error => response.end( error ) );
+ } else {
+ response.writeHead( 206, {
+ "Content-Range": "bytes */0",
+ "Accept-Ranges": "bytes",
+ "Content-Length": 0,
+ "Content-Type": m
+ } );
+ response.end();
+ }
+ } );
+ }
+ } else {
+ response.end('');
+ }
+} //close OnStreamRequest
+
+
+function HandlePersistenceUpload( request, response, parsedRequest, segments ) {
+ if ( segments.length >= 2 ) {
+ var streamPath;
+ var form = new formidable.IncomingForm();
+ form.keepExtensions = true;
+ // create the path in file system if not there
+ var relPath = parsedRequest[ 'public_path' ];
+ for (var i = 1; i < segments.length; i++) {
+ var relPath = helpers.JoinPath( relPath, segments[i] );
+ }
+ CreateDirectory( relPath );
+ form.on('fileBegin', function(name, file) {
+ // redirect file to the specified path
+ var filename = file.name.replace(/\s+/g, '_');
+ file.path = helpers.JoinPath( './documents', relPath, filename);
+ streamPath = helpers.JoinPath(streamStr, 'documents', relPath, filename);
+ });
+ // parse the file and then send response
+ form.parse(request, function(err, fields, files) {
+ if ( ! err ) {
+
+ // If this is a .zip file, unzip it first
+ if ( streamPath.endsWith( ".zip" ) ) {
+ var zip = new AdmZip( files.file.path );
+ zip.extractAllTo( helpers.JoinPath( './documents', relPath ) );
+ streamPath = streamPath.substring( 0, streamPath.indexOf( ".zip" ) );
+ }
+ response.writeHead(200, {'content-type': 'text/plain'});
+ response.end(streamPath);
+ } else {
+ console.log("Error parsing file: " + err);
+ response.writeHead(500);
+ response.end();
+ }
+ });
+ return true;
+ }
+ return false;
+} // close HandlePersistenceUpload
+
+function CreateDirectory( relPath ) {
+ var application_segments = helpers.GenerateSegments( relPath );
+ var current_directory = "./documents";
+ while ( application_segments.length > 0 ) {
+ current_directory = helpers.JoinPath( current_directory, application_segments.shift() );
+ if ( ! helpers.IsDirectory( current_directory ) ) {
+ fs.mkdirSync( current_directory );
+ }
+ }
+}
+
+
+// The Serve function takes the nodeJS request, nodeJS response and the parsedRequest, and
+// attempts to see if it is a properly formed stream related request.
+function Serve( request, response, parsedRequest ) {
+ if ( parsedRequest[ 'private_path' ] ) {
+ var segments = helpers.GenerateSegments( parsedRequest[ 'private_path' ] );
+ if ( segments.length > 0 ) {
+ switch ( segments[ 0 ] ) {
+ case uploadStr:
+ if ( request.method == "POST" ) {
+ var result = HandlePersistenceUpload( request, response, parsedRequest, segments );
+ return result;
+ }
+ return false;
+ case streamStr:
+ if ( request.method == "GET" ) {
+ OnStreamRequest( request, response, parsedRequest, segments );
+ return true;
+ }
+ return false;
+ }
+ }
+ }
+ return false;
+}
+exports.Serve = Serve;
\ No newline at end of file
diff --git a/lib/nodejs/vwf.js b/lib/nodejs/vwf.js
index 6a0f383b4..d9de6c1f1 100755
--- a/lib/nodejs/vwf.js
+++ b/lib/nodejs/vwf.js
@@ -7,7 +7,9 @@ var parseurl = require( './parse-url' ),
servehandler = require( './serve-handler' ),
helpers = require( './helpers' ),
application = require( './application' ),
- url = require( 'url' );
+ url = require( 'url' ),
+ lobby = require( './lobby' ),
+ finalhandler = require( 'finalhandler' );
// HandleParsableRequest takes the incoming request, and uses the helper library functions to parse the
// URL into its 'public_path, application, instance and private_path' components, and then attempts to redirect
@@ -16,6 +18,10 @@ var parseurl = require( './parse-url' ),
function HandleParsableRequest( request, response ) {
var parsedRequest = parseurl.Process( url.parse(request.url).pathname );
+ if ( ! parsedRequest ) {
+ return false;
+ }
+
// Used to check if the URL referer was an application instance. Components added by the "includes" keyword
// in yaml are loaded using jQuery which appends a query parameter to handle the callback. Checking the referer
// allows those URLs to be handled correctly, instead of treating them as a new application that needs an instance ID.
@@ -124,25 +130,31 @@ function HandleFileRequest( request, response ) {
}
// Serve is the top level function for serving requests. It first attempts to
-// serve the request based on parsing the incoming URL.
+// serve the request using the the lobby, which is an Express application.
+// Lobby errors are handled using finalhandler in the same way that Express would.
+// Otherwise, it attempts to serve the request based on parsing the incoming URL.
// If that fails, it continues to attempt to serve the request as a 'proxy' request,
// if that also does not serve anything to the request, then an attempt is made
// to handle the request as a simple direct request for a file within the public
// directory structure.
// If all that fails, serve up a 404 response since the request was not handled.
function Serve( request, response ) {
- var handledRequest = HandleParsableRequest( request, response );
- if ( ! ( handledRequest ) ) {
- handledRequest = HandleProxyRequest( request, response );
- }
- if ( ! ( handledRequest ) ) {
- handledRequest = HandleFileRequest( request, response );
- }
- if ( ! ( handledRequest ) ) {
- global.log("404 : " + url.parse( request.url ).pathname )
- serve._404( response, "404.html" );
-
- }
+ lobby( request, response, function( err ) {
+ if ( err ) {
+ finalhandler( request, response, {
+ env: lobby.get('env')
+ } )( err );
+ } else {
+ var handledRequest =
+ HandleParsableRequest( request, response ) ||
+ HandleProxyRequest( request, response ) ||
+ HandleFileRequest( request, response );
+ if ( ! ( handledRequest ) ) {
+ global.log("404 : " + url.parse( request.url ).pathname )
+ serve._404( response, "404.html" );
+ }
+ }
+ } );
}
-exports.Serve = Serve;
\ No newline at end of file
+exports.Serve = Serve;
diff --git a/lib/nodejs/xapi.js b/lib/nodejs/xapi.js
new file mode 100644
index 000000000..79e305053
--- /dev/null
+++ b/lib/nodejs/xapi.js
@@ -0,0 +1,207 @@
+"use strict";
+
+/// xAPI logging utilities. Record user experience events in an xAPI-format log.
+
+var Promise = require( "bluebird" ),
+ fs = Promise.promisifyAll( require( "fs" ) ),
+ mkdirp = Promise.promisify( require( "mkdirp" ) ),
+ _ = require( "lodash" );
+
+/// Record scenario or session creation in the xAPI log.
+
+exports.logCreation = function( state, application, document, user ) {
+
+ var descriptor = {
+ state:
+ _.get( state, "nodes[1].children.scenarioController.properties", {} ),
+ document: {
+ uri: application + "/0000000000000000/load/" + document + "/" }
+ };
+
+ var statement = JSON.stringify( {
+ actor: xapiActor( user ),
+ verb: xapiVerbs.imported,
+ object: xapiActivity( descriptor ),
+ stored: ( new Date ).toISOString(),
+ } ) + "\n";
+
+ mkdirp( "log/" ).then( () => fs.appendFileAsync( "log/xapi-statements.json", statement ) );
+
+};
+
+exports.logClient = function( state, application, document, instance, user, joining, only ) {
+
+ var descriptor = {
+ state: state &&
+ _.get( state, "nodes[1].children.scenarioController.properties", {} ),
+ document: document && {
+ uri: application + "/0000000000000000/load/" + document + "/" },
+ instance: instance &&
+ instance.replace( /index.vwf\//, "" ) + "/",
+ };
+
+ var statements = "";
+
+ if ( only && joining ) {
+ statements += JSON.stringify( {
+ actor: xapiActor( user ),
+ verb: xapiVerbs.launched,
+ object: xapiActivity( descriptor ),
+ result: document && instance &&
+ { response: descriptor.instance },
+ stored: ( new Date ).toISOString(),
+ } ) + "\n";
+ }
+
+ statements += JSON.stringify( {
+ actor: xapiActor( user ),
+ verb: joining ? xapiVerbs.attended : xapiVerbs.exited,
+ object: xapiActivity( descriptor ),
+ stored: ( new Date ).toISOString(),
+ } ) + "\n";
+
+ if ( only && ! joining ) {
+ statements += JSON.stringify( {
+ actor: xapiActor( user ),
+ verb: xapiVerbs.terminated,
+ object: xapiActivity( descriptor ),
+ stored: ( new Date ).toISOString(),
+ } ) + "\n";
+ }
+
+ mkdirp( "log/" ).then( () => fs.appendFileAsync( "log/xapi-statements.json", statements ) );
+
+};
+
+/// Return an xAPI [Actor] (https://github.com/adlnet/xAPI-Spec/blob/master/xAPI.md#actor)
+/// description of the user.
+///
+/// @param {Object} user
+///
+/// @returns {Object} xAPI Actor
+
+function xapiActor( user ) {
+
+ var name = n( user.first_name ) + n( user.middle_initial, "." ) +
+ n( user.last_name );
+
+ var account = a( user.first_name ) + a( user.middle_initial ) +
+ a( user.last_name ) + '[' + ( user.instructor ? 'Instructor' : 'Student' ) + '] ';
+
+ name = name.slice( 0, -1 ) || "Unknown"
+ account = account.slice( 0, -1 ) || "unknown"
+
+ return {
+ name:
+ name,
+ account: {
+ homePage: "http://itdg.example.com",
+ name: account
+ }
+ };
+
+ function n( string, terminator ) {
+ if ( string ) {
+ return string + ( terminator || "" ) + " ";
+ } else {
+ return "";
+ }
+ }
+
+ function a( string ) {
+ var slug =
+ (string || "").replace( /[^0-9A-Za-z]+/, "-" ).toLowerCase();
+ if ( slug ) {
+ return slug + ".";
+ } else {
+ return "";
+ }
+ }
+
+}
+
+/// A selection of xAPI [Verbs] (https://github.com/adlnet/xAPI-Spec/blob/master/xAPI.md#verb) from
+/// the [ADL Controlled Vocabulary] (http://xapi.vocab.pub/datasets/adl/).
+
+var xapiVerbs = {
+
+ attended: {
+ id: "http://adlnet.gov/expapi/verbs/attended",
+ display: { "en-US": "attended" },
+ },
+ completed: {
+ id: "http://adlnet.gov/expapi/verbs/completed",
+ display: { "en-US": "completed" },
+ },
+ exited: {
+ id: "http://adlnet.gov/expapi/verbs/exited",
+ display: { "en-US": "exited" },
+ },
+ imported: {
+ id: "http://adlnet.gov/expapi/verbs/imported",
+ display: { "en-US": "imported" },
+ },
+ initialized: {
+ id: "http://adlnet.gov/expapi/verbs/initialized",
+ display: { "en-US": "initialized" },
+ },
+ interacted: {
+ id: "http://adlnet.gov/expapi/verbs/interacted",
+ display: { "en-US": "interacted" },
+ },
+ launched: {
+ id: "http://adlnet.gov/expapi/verbs/launched",
+ display: { "en-US": "launched" },
+ },
+ "logged-in": {
+ id: "https://w3id.org/xapi/adl/verbs/logged-in",
+ display: { "en-US": "logged-in" },
+ },
+ "logged-out": {
+ id: "https://w3id.org/xapi/adl/verbs/logged-out",
+ display: { "en-US": "logged-out" },
+ },
+ terminated: {
+ id: "http://adlnet.gov/expapi/verbs/terminated",
+ display: { "en-US": "terminated" },
+ },
+
+};
+
+/// Return an xAPI [Activity Object] (https://github.com/adlnet/xAPI-Spec/blob/master/xAPI.md#object)
+/// for the a scenario or session document or instance.
+///
+/// @param {Object} descriptor
+/// `manifest.js`-style scenario or session descriptor containing the `state` and `document`
+/// and/or `instance` fields.
+///
+/// @returns {Object} xAPI Activity Object
+
+function xapiActivity( descriptor ) {
+
+ return {
+ id: ( descriptor.document && descriptor.document.uri ) ||
+ descriptor.instance,
+ definition: {
+ name: descriptor.state && { "en-US": title( descriptor ) },
+ type: "http://adlnet.gov/expapi/activities/simulation",
+ }
+ };
+
+ function title( descriptor ) {
+
+ var state = descriptor.state || {},
+ classroom = state.classroom || {};
+
+ if ( classroom.company || classroom.platoon || classroom.unit ) {
+ return ( state.scenarioTitle || "Untitled Session" ) + ", " +
+ "Company " + ( classroom.company || "-" ) + " " +
+ "Platoon " + ( classroom.platoon || "-" ) + " " +
+ "Unit " + ( classroom.unit || "-" );
+ } else {
+ return state.scenarioTitle || "Untitled Session";
+ }
+
+ }
+
+}
diff --git a/lib/nodejs/yaml-cache.js b/lib/nodejs/yaml-cache.js
new file mode 100644
index 000000000..e3e310b6e
--- /dev/null
+++ b/lib/nodejs/yaml-cache.js
@@ -0,0 +1,138 @@
+// yaml-cache.js
+// This file contains the implementation of the yaml cache system used by the VWF nodeJS server.
+
+
+var fs = require( 'fs' ),
+ YAML = require( 'js-yaml' ),
+ url = require( 'url' ),
+ helpers = require( './helpers' );
+
+
+// Helper function to generate a hash for a string.
+function hash( str ) {
+ return require( 'crypto' ).createHash( 'md5' ).update( str ).digest( "hex" );
+}
+
+function _YamlCache( ) {
+ this.filesMap = { };
+ this.enabled = true;
+ this.clear = function ( ) {
+ this.filesMap = { };
+ };
+
+ // Function for getting a file from the file cache.
+ // Asynchronous, returns file to the callback function.
+ // Passes null to the callback function if there is no such file.
+ this.getJsonStr = function ( path, callback ) {
+
+ function getLastModifiedTime ( path ) {
+ try {
+ var stats = fs.statSync(path);
+ return stats.mtime;
+ }
+ catch(err) {
+ return null;
+ }
+ }
+
+ function updateSources(obj, key, dirPath) {
+ if (obj instanceof Array) {
+ for(var i = 0; i < obj.length; i++) {
+ updateSources(obj[i], key, dirPath);
+ }
+ } else {
+ for(var prop in obj) {
+ if(prop === key) {
+ obj[prop] = getPathWithMTime(obj[prop], dirPath);
+ return;
+ }
+ if(obj[prop] instanceof Object || obj[prop] instanceof Array) {
+ updateSources(obj[prop], key, dirPath);
+ }
+ }
+ }
+ }
+ if ( this.filesMap[ path ] ) {
+ callback( this.filesMap[ path ] );
+ return;
+ }
+
+ function getPathWithMTime( file, dirPath ){
+ var tempFile = file;
+ var updatedURL = url.parse( tempFile ).pathname;
+ if ( file.indexOf('vwf.example.com') >= 0 ) {
+ tempFile = helpers.JoinPath( global.vwfRoot, "support/proxy/vwf.example.com/", updatedURL );
+ } else if (dirPath) {
+ tempFile = dirPath + tempFile;
+ }
+ var mtime = getLastModifiedTime(tempFile);
+ return file + (mtime ? '?m=' + mtime.getTime() : '');
+ }
+
+
+ // if got here, have no record of this file yet.
+ var datatype = 'utf8';
+ var data = fs.readFileSync( path, datatype );
+ var stats = fs.statSync( path );
+
+ if ( data ) {
+ // Remove the Byte Order Mark (BOM) if one exists
+ var file = data.replace( /^\uFEFF/, '' );
+
+ //global.log(tf);
+ try {
+ // this check is done to avoid traversing entire json tree for every file
+ var containsSourceKey = file.indexOf('source') >= 0;
+ var yamlObj = YAML.load( file );
+ if ( containsSourceKey ) {
+ var dirPath = path.substring( 0, path.lastIndexOf( '\\' ) + 1 );
+ updateSources(yamlObj, 'source', dirPath);
+ }
+ var deYAML = JSON.stringify( yamlObj );
+ this.filesMap[ path ] = deYAML;
+ callback(deYAML);
+ return;
+ } catch ( e ) {
+ global.log( "error parsing YAML " + filename );
+ _404( response );
+ callback(null);
+ return;
+ }
+ }
+ // File was not in cache, and did not exists, return null.
+ callback( null );
+ };
+ //Parse and serve a YAML file
+ //move this stuff to yaml-cache
+ this.ServeYAML = function ( request, filename, response, URL ) {
+
+ YamlCache.getJsonStr( filename, function ( data ) {
+ if ( !data ) {
+ response.writeHead( 500, {
+ "Content-Type": "text/plain"
+ } );
+ response.write( 'file load error' + '\n' );
+ response.end( );
+ return;
+ }
+
+ var type = "application/json";
+
+ var callback = URL.query.callback;
+
+ if ( callback ) {
+ data = callback + "(" + data + ")";
+ type = "application/javascript";
+ }
+
+ response.writeHead( 200, {
+ "Content-Type": type
+ } );
+ response.write( data, "utf8" );
+ response.end();
+ } );
+ };
+}
+
+
+exports._YamlCache = _YamlCache;
\ No newline at end of file
diff --git a/log/xapi-statements.json b/log/xapi-statements.json
new file mode 100644
index 000000000..4d4c513b3
--- /dev/null
+++ b/log/xapi-statements.json
@@ -0,0 +1,14 @@
+{"actor":{"name":"qewr","account":{"homePage":"http://itdg.example.com","name":"qewr.[Instructor]"}},"verb":{"id":"http://adlnet.gov/expapi/verbs/imported","display":{"en-US":"imported"}},"object":{"id":"/ITDG/0000000000000000/load/newscen/","definition":{"name":{"en-US":"newscen"},"type":"http://adlnet.gov/expapi/activities/simulation"}},"stored":"2018-07-02T19:28:51.461Z"}
+{"actor":{"name":"qewr","account":{"homePage":"http://itdg.example.com","name":"qewr.[Instructor]"}},"verb":{"id":"http://adlnet.gov/expapi/verbs/launched","display":{"en-US":"launched"}},"object":{"id":"/ITDG/0000000000000000/load/newscen/","definition":{"name":{"en-US":"newscen"},"type":"http://adlnet.gov/expapi/activities/simulation"}},"result":{"response":"/ITDG/P9YHBGn0ERGPsUIr/"},"stored":"2018-07-02T19:28:55.061Z"}
+{"actor":{"name":"qewr","account":{"homePage":"http://itdg.example.com","name":"qewr.[Instructor]"}},"verb":{"id":"http://adlnet.gov/expapi/verbs/attended","display":{"en-US":"attended"}},"object":{"id":"/ITDG/0000000000000000/load/newscen/","definition":{"name":{"en-US":"newscen"},"type":"http://adlnet.gov/expapi/activities/simulation"}},"stored":"2018-07-02T19:28:55.061Z"}
+{"actor":{"name":"qewr","account":{"homePage":"http://itdg.example.com","name":"qewr.[Instructor]"}},"verb":{"id":"http://adlnet.gov/expapi/verbs/exited","display":{"en-US":"exited"}},"object":{"id":"/ITDG/0000000000000000/load/newscen/","definition":{"type":"http://adlnet.gov/expapi/activities/simulation"}},"stored":"2018-07-02T19:46:20.652Z"}
+{"actor":{"name":"qewr","account":{"homePage":"http://itdg.example.com","name":"qewr.[Instructor]"}},"verb":{"id":"http://adlnet.gov/expapi/verbs/terminated","display":{"en-US":"terminated"}},"object":{"id":"/ITDG/0000000000000000/load/newscen/","definition":{"type":"http://adlnet.gov/expapi/activities/simulation"}},"stored":"2018-07-02T19:46:20.653Z"}
+{"actor":{"name":"quad","account":{"homePage":"http://itdg.example.com","name":"quad.[Instructor]"}},"verb":{"id":"http://adlnet.gov/expapi/verbs/launched","display":{"en-US":"launched"}},"object":{"id":"/ITDG/0000000000000000/load/newscen/","definition":{"name":{"en-US":"newscen"},"type":"http://adlnet.gov/expapi/activities/simulation"}},"result":{"response":"/ITDG/fhFjDMGutbFkBfa0/"},"stored":"2018-07-03T14:03:29.326Z"}
+{"actor":{"name":"quad","account":{"homePage":"http://itdg.example.com","name":"quad.[Instructor]"}},"verb":{"id":"http://adlnet.gov/expapi/verbs/attended","display":{"en-US":"attended"}},"object":{"id":"/ITDG/0000000000000000/load/newscen/","definition":{"name":{"en-US":"newscen"},"type":"http://adlnet.gov/expapi/activities/simulation"}},"stored":"2018-07-03T14:03:29.326Z"}
+{"actor":{"name":"dr","account":{"homePage":"http://itdg.example.com","name":"dr.[Instructor]"}},"verb":{"id":"http://adlnet.gov/expapi/verbs/imported","display":{"en-US":"imported"}},"object":{"id":"/ITDG/0000000000000000/load//","definition":{"name":{"en-US":"Untitled Session"},"type":"http://adlnet.gov/expapi/activities/simulation"}},"stored":"2018-07-05T13:50:44.170Z"}
+{"actor":{"name":"dr","account":{"homePage":"http://itdg.example.com","name":"dr.[Instructor]"}},"verb":{"id":"http://adlnet.gov/expapi/verbs/imported","display":{"en-US":"imported"}},"object":{"id":"/ITDG/0000000000000000/load//","definition":{"name":{"en-US":"Untitled Session"},"type":"http://adlnet.gov/expapi/activities/simulation"}},"stored":"2018-07-05T13:50:46.305Z"}
+{"actor":{"name":"dr","account":{"homePage":"http://itdg.example.com","name":"dr.[Instructor]"}},"verb":{"id":"http://adlnet.gov/expapi/verbs/imported","display":{"en-US":"imported"}},"object":{"id":"/ITDG/0000000000000000/load//","definition":{"name":{"en-US":"Untitled Session"},"type":"http://adlnet.gov/expapi/activities/simulation"}},"stored":"2018-07-05T14:14:20.895Z"}
+{"actor":{"name":"dr","account":{"homePage":"http://itdg.example.com","name":"dr.[Instructor]"}},"verb":{"id":"http://adlnet.gov/expapi/verbs/imported","display":{"en-US":"imported"}},"object":{"id":"/ITDG/0000000000000000/load//","definition":{"name":{"en-US":"Untitled Session"},"type":"http://adlnet.gov/expapi/activities/simulation"}},"stored":"2018-07-05T14:14:23.418Z"}
+{"actor":{"name":"dr","account":{"homePage":"http://itdg.example.com","name":"dr.[Instructor]"}},"verb":{"id":"http://adlnet.gov/expapi/verbs/imported","display":{"en-US":"imported"}},"object":{"id":"/ITDG/0000000000000000/load//","definition":{"name":{"en-US":"Untitled Session"},"type":"http://adlnet.gov/expapi/activities/simulation"}},"stored":"2018-07-05T14:15:38.866Z"}
+{"actor":{"name":"dr","account":{"homePage":"http://itdg.example.com","name":"dr.[Instructor]"}},"verb":{"id":"http://adlnet.gov/expapi/verbs/imported","display":{"en-US":"imported"}},"object":{"id":"/ITDG/0000000000000000/load//","definition":{"name":{"en-US":"Untitled Session"},"type":"http://adlnet.gov/expapi/activities/simulation"}},"stored":"2018-07-05T14:19:03.095Z"}
+{"actor":{"name":"dr","account":{"homePage":"http://itdg.example.com","name":"dr.[Instructor]"}},"verb":{"id":"http://adlnet.gov/expapi/verbs/imported","display":{"en-US":"imported"}},"object":{"id":"/ITDG/0000000000000000/load/dgfdg/","definition":{"name":{"en-US":"dgfdg"},"type":"http://adlnet.gov/expapi/activities/simulation"}},"stored":"2018-07-05T14:32:04.737Z"}
diff --git a/node_vwf.js b/node_vwf.js
index bf731912c..1b8b740bd 100644
--- a/node_vwf.js
+++ b/node_vwf.js
@@ -148,9 +148,12 @@ function startVWF() {
return null;
}
}
- }
+ },
+ 'destroy buffer size': Infinity, // https://github.com/socketio/socket.io/issues/1592#issuecomment-45885463; should be fixed in socket.io 1.0
} );
socketManager.set( 'transports', [ 'websocket' ] );
+ socketManager.set( 'heartbeat timeout', 3600 ); // ! hour timeout
+ socketManager.set( 'close timeout', 3600 ); // 1 hour timeout
socketManager.sockets.on( 'connection', reflector.OnConnection );
}
diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json
index ce3b268cd..5770e3973 100644
--- a/npm-shrinkwrap.json
+++ b/npm-shrinkwrap.json
@@ -1,149 +1,2581 @@
{
"name": "vwf",
"version": "0.0.0",
+ "lockfileVersion": 1,
+ "requires": true,
"dependencies": {
+ "adm-zip": {
+ "version": "0.4.7",
+ "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.4.7.tgz",
+ "integrity": "sha1-hgbCy/HEJs6MjsABdER/1Jtur8E="
+ },
+ "amdefine": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz",
+ "integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU="
+ },
+ "amqplib": {
+ "version": "0.5.1",
+ "resolved": "https://registry.npmjs.org/amqplib/-/amqplib-0.5.1.tgz",
+ "integrity": "sha1-fMz+ur5WwumE6noiQ/fO/m+/xs8=",
+ "requires": {
+ "bitsyntax": "0.0.4",
+ "bluebird": "3.4.7",
+ "buffer-more-ints": "0.0.2",
+ "readable-stream": "1.1.14"
+ },
+ "dependencies": {
+ "bitsyntax": {
+ "version": "0.0.4",
+ "resolved": "https://registry.npmjs.org/bitsyntax/-/bitsyntax-0.0.4.tgz",
+ "integrity": "sha1-6xDMb4K4xJDj6FaY8H6D1G4MuoI=",
+ "requires": {
+ "buffer-more-ints": "0.0.2"
+ }
+ },
+ "bluebird": {
+ "version": "3.4.7",
+ "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.4.7.tgz",
+ "integrity": "sha1-9y12C+Cbf3bQjtj66Ysomo0F+rM="
+ },
+ "buffer-more-ints": {
+ "version": "0.0.2",
+ "resolved": "https://registry.npmjs.org/buffer-more-ints/-/buffer-more-ints-0.0.2.tgz",
+ "integrity": "sha1-JrOIXRD6E9t/wBquOquHAZngEkw="
+ },
+ "readable-stream": {
+ "version": "1.1.14",
+ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz",
+ "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=",
+ "requires": {
+ "core-util-is": "1.0.2",
+ "inherits": "2.0.3",
+ "isarray": "0.0.1",
+ "string_decoder": "0.10.31"
+ },
+ "dependencies": {
+ "core-util-is": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
+ "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac="
+ },
+ "inherits": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
+ "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4="
+ },
+ "isarray": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz",
+ "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8="
+ },
+ "string_decoder": {
+ "version": "0.10.31",
+ "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz",
+ "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ="
+ }
+ }
+ }
+ }
+ },
+ "append-field": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/append-field/-/append-field-0.1.0.tgz",
+ "integrity": "sha1-bdxY+gg8e8VF08WZWygwzCNm1Eo="
+ },
+ "archiver": {
+ "version": "0.11.0",
+ "resolved": "https://registry.npmjs.org/archiver/-/archiver-0.11.0.tgz",
+ "integrity": "sha1-mBd9p6bAGSt/J5jzDNbquKvXZpA=",
+ "requires": {
+ "async": "0.9.2",
+ "buffer-crc32": "0.2.13",
+ "glob": "3.2.11",
+ "lazystream": "0.1.0",
+ "lodash": "2.4.2",
+ "readable-stream": "1.0.34",
+ "tar-stream": "0.4.7",
+ "zip-stream": "0.4.1"
+ },
+ "dependencies": {
+ "async": {
+ "version": "0.9.2",
+ "resolved": "https://registry.npmjs.org/async/-/async-0.9.2.tgz",
+ "integrity": "sha1-rqdNXmHB+JlhO/ZL2mbUx48v0X0="
+ },
+ "glob": {
+ "version": "3.2.11",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-3.2.11.tgz",
+ "integrity": "sha1-Spc/Y1uRkPcV0QmH1cAP0oFevj0=",
+ "requires": {
+ "inherits": "2.0.3",
+ "minimatch": "0.3.0"
+ }
+ },
+ "lodash": {
+ "version": "2.4.2",
+ "resolved": "https://registry.npmjs.org/lodash/-/lodash-2.4.2.tgz",
+ "integrity": "sha1-+t2DS5aDBz2hebPq5tnA0VBT9z4="
+ },
+ "minimatch": {
+ "version": "0.3.0",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-0.3.0.tgz",
+ "integrity": "sha1-J12O2qxPG7MyZHIInnlJyDlGmd0=",
+ "requires": {
+ "lru-cache": "2.7.3",
+ "sigmund": "1.0.1"
+ }
+ }
+ }
+ },
+ "async": {
+ "version": "https://registry.npmjs.org/async/-/async-0.2.10.tgz",
+ "integrity": "sha1-trvgsGdLnXGXCMo43owjfLUmw9E="
+ },
+ "bl": {
+ "version": "0.9.5",
+ "resolved": "https://registry.npmjs.org/bl/-/bl-0.9.5.tgz",
+ "integrity": "sha1-wGt5evCF6gC8Unr8jvzxHeIjIFQ=",
+ "requires": {
+ "readable-stream": "1.0.34"
+ }
+ },
+ "bluebird": {
+ "version": "https://registry.npmjs.org/bluebird/-/bluebird-3.4.0.tgz",
+ "integrity": "sha1-KLhHk1qLtW1/8cfro2MbCdWkmyQ="
+ },
+ "body-parser": {
+ "version": "https://registry.npmjs.org/body-parser/-/body-parser-1.15.1.tgz",
+ "integrity": "sha1-m87vBmm4+LlD8K2M5dlXFr10D9I=",
+ "requires": {
+ "bytes": "2.3.0",
+ "content-type": "1.0.2",
+ "debug": "2.2.0",
+ "depd": "1.1.0",
+ "http-errors": "1.4.0",
+ "iconv-lite": "0.4.13",
+ "on-finished": "2.3.0",
+ "qs": "6.1.0",
+ "raw-body": "2.1.7",
+ "type-is": "1.6.13"
+ },
+ "dependencies": {
+ "bytes": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/bytes/-/bytes-2.3.0.tgz",
+ "integrity": "sha1-1baAoWW2IBc5rLYRVCqrwtjOsHA="
+ },
+ "content-type": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.2.tgz",
+ "integrity": "sha1-t9ETrueo3Se9IRM8TcJSnfFyHu0="
+ },
+ "debug": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz",
+ "integrity": "sha1-+HBX6ZWxofauaklgZkE3vFbwOdo=",
+ "requires": {
+ "ms": "0.7.1"
+ },
+ "dependencies": {
+ "ms": {
+ "version": "0.7.1",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.1.tgz",
+ "integrity": "sha1-nNE8A62/8ltl7/3nzoZO6VIBcJg="
+ }
+ }
+ },
+ "depd": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.0.tgz",
+ "integrity": "sha1-4b2Cxqq2ztlluXuIsX7T5SjKGMM="
+ },
+ "http-errors": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.4.0.tgz",
+ "integrity": "sha1-bAJC3qaz33r9oVPHEImzHG6Cqr8=",
+ "requires": {
+ "inherits": "2.0.1",
+ "statuses": "1.3.0"
+ },
+ "dependencies": {
+ "inherits": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz",
+ "integrity": "sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE="
+ },
+ "statuses": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.3.0.tgz",
+ "integrity": "sha1-jlV1jLIOdoLB9Pzo3KswvwHR4Ho="
+ }
+ }
+ },
+ "iconv-lite": {
+ "version": "0.4.13",
+ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.13.tgz",
+ "integrity": "sha1-H4irpKsLFQjoMSrMOTRfNumS4vI="
+ },
+ "on-finished": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz",
+ "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=",
+ "requires": {
+ "ee-first": "1.1.1"
+ },
+ "dependencies": {
+ "ee-first": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
+ "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0="
+ }
+ }
+ },
+ "qs": {
+ "version": "6.1.0",
+ "resolved": "https://registry.npmjs.org/qs/-/qs-6.1.0.tgz",
+ "integrity": "sha1-7B0WJrJCeNmfD99FSeUk4k7O6yY="
+ },
+ "raw-body": {
+ "version": "2.1.7",
+ "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.1.7.tgz",
+ "integrity": "sha1-rf6s4uT7MJgFgBTQjActzFl1h3Q=",
+ "requires": {
+ "bytes": "2.4.0",
+ "iconv-lite": "0.4.13",
+ "unpipe": "1.0.0"
+ },
+ "dependencies": {
+ "bytes": {
+ "version": "2.4.0",
+ "resolved": "https://registry.npmjs.org/bytes/-/bytes-2.4.0.tgz",
+ "integrity": "sha1-fZcZb51br39pNeJZhVSe3SpsIzk="
+ },
+ "unpipe": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
+ "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw="
+ }
+ }
+ },
+ "type-is": {
+ "version": "1.6.13",
+ "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.13.tgz",
+ "integrity": "sha1-boO6e8MM0zp7sLf7AHN6IIW/nQg=",
+ "requires": {
+ "media-typer": "0.3.0",
+ "mime-types": "2.1.11"
+ },
+ "dependencies": {
+ "media-typer": {
+ "version": "0.3.0",
+ "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
+ "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g="
+ },
+ "mime-types": {
+ "version": "2.1.11",
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.11.tgz",
+ "integrity": "sha1-wlnEcb2oCKhdbNGTtDCl+uRHOzw=",
+ "requires": {
+ "mime-db": "1.23.0"
+ },
+ "dependencies": {
+ "mime-db": {
+ "version": "1.23.0",
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.23.0.tgz",
+ "integrity": "sha1-oxtAcK2uon1zLqMzdApk0OyaZlk="
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "browser-stdout": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.0.tgz",
+ "integrity": "sha1-81HTKWnTL6XXpVZxVCY9korjvR8=",
+ "dev": true
+ },
+ "buffer-crc32": {
+ "version": "0.2.13",
+ "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz",
+ "integrity": "sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI="
+ },
+ "buffer-shims": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/buffer-shims/-/buffer-shims-1.0.0.tgz",
+ "integrity": "sha1-mXjOMXOIxkmth5MCjDR37wRKi1E="
+ },
+ "busboy": {
+ "version": "0.2.14",
+ "resolved": "https://registry.npmjs.org/busboy/-/busboy-0.2.14.tgz",
+ "integrity": "sha1-bCpiLvz0fFe7vh4qnDetNseSVFM=",
+ "requires": {
+ "dicer": "0.2.5",
+ "readable-stream": "1.1.14"
+ },
+ "dependencies": {
+ "readable-stream": {
+ "version": "1.1.14",
+ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz",
+ "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=",
+ "requires": {
+ "core-util-is": "1.0.2",
+ "inherits": "2.0.3",
+ "isarray": "0.0.1",
+ "string_decoder": "0.10.31"
+ }
+ }
+ }
+ },
+ "commander": {
+ "version": "2.11.0",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-2.11.0.tgz",
+ "integrity": "sha512-b0553uYA5YAEGgyYIGYROzKQ7X5RAqedkfjiZxwi0kL1g3bOaBNNZfYkzt/CL0umgD5wc9Jec2FbB98CjkMRvQ==",
+ "dev": true
+ },
+ "compress-commons": {
+ "version": "0.1.6",
+ "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-0.1.6.tgz",
+ "integrity": "sha1-DHQIcP3ljLpRbwrAyCLjOguF36M=",
+ "requires": {
+ "buffer-crc32": "0.2.13",
+ "crc32-stream": "0.3.4",
+ "readable-stream": "1.0.34"
+ }
+ },
+ "concat-stream": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.0.tgz",
+ "integrity": "sha1-CqxmL9Ur54lk1VMvaUeE5wEQrPc=",
+ "requires": {
+ "inherits": "2.0.3",
+ "readable-stream": "2.2.6",
+ "typedarray": "0.0.6"
+ },
+ "dependencies": {
+ "isarray": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
+ "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE="
+ },
+ "readable-stream": {
+ "version": "2.2.6",
+ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.2.6.tgz",
+ "integrity": "sha1-i0Ou125xSDk40SqNRsbPGgCx+BY=",
+ "requires": {
+ "buffer-shims": "1.0.0",
+ "core-util-is": "1.0.2",
+ "inherits": "2.0.3",
+ "isarray": "1.0.0",
+ "process-nextick-args": "1.0.7",
+ "string_decoder": "0.10.31",
+ "util-deprecate": "1.0.2"
+ }
+ }
+ }
+ },
+ "config": {
+ "version": "https://registry.npmjs.org/config/-/config-1.21.0.tgz",
+ "integrity": "sha1-g3GcAmOmaeS8TMnJ5YGM2ZajLLk=",
+ "requires": {
+ "json5": "0.4.0"
+ },
+ "dependencies": {
+ "json5": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/json5/-/json5-0.4.0.tgz",
+ "integrity": "sha1-BUNS5MTIDIbAkjh31EneF2pzLI0="
+ }
+ }
+ },
+ "cookie-parser": {
+ "version": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.3.tgz",
+ "integrity": "sha1-D+MfoZ0AC5X0qt8fU/3CuKIDuqU=",
+ "requires": {
+ "cookie": "0.3.1",
+ "cookie-signature": "1.0.6"
+ },
+ "dependencies": {
+ "cookie": {
+ "version": "0.3.1",
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz",
+ "integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s="
+ },
+ "cookie-signature": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
+ "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw="
+ }
+ }
+ },
+ "cookie-session": {
+ "version": "https://registry.npmjs.org/cookie-session/-/cookie-session-2.0.0-alpha.1.tgz",
+ "integrity": "sha1-AmbRse7nynp49Td3oKCwVzvjLV8=",
+ "requires": {
+ "cookies": "0.5.1",
+ "debug": "2.2.0",
+ "on-headers": "1.0.1"
+ },
+ "dependencies": {
+ "cookies": {
+ "version": "0.5.1",
+ "resolved": "https://registry.npmjs.org/cookies/-/cookies-0.5.1.tgz",
+ "integrity": "sha1-JWDDBP6PjL0ALgi5WZ0udHnTcpg=",
+ "requires": {
+ "keygrip": "1.0.1"
+ },
+ "dependencies": {
+ "keygrip": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/keygrip/-/keygrip-1.0.1.tgz",
+ "integrity": "sha1-sC+kgW7vIajEs1yp5Skh/8iaMOk="
+ }
+ }
+ },
+ "debug": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz",
+ "integrity": "sha1-+HBX6ZWxofauaklgZkE3vFbwOdo=",
+ "requires": {
+ "ms": "0.7.1"
+ },
+ "dependencies": {
+ "ms": {
+ "version": "0.7.1",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.1.tgz",
+ "integrity": "sha1-nNE8A62/8ltl7/3nzoZO6VIBcJg="
+ }
+ }
+ },
+ "on-headers": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.1.tgz",
+ "integrity": "sha1-ko9dD0cNSTQmUepnlLCFfBAGk/c="
+ }
+ }
+ },
+ "core-util-is": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
+ "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac="
+ },
+ "crc32-stream": {
+ "version": "0.3.4",
+ "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-0.3.4.tgz",
+ "integrity": "sha1-c7wltF+sHbZjIjGnv86JJ+nwZVI=",
+ "requires": {
+ "buffer-crc32": "0.2.13",
+ "readable-stream": "1.0.34"
+ }
+ },
"crypto": {
- "version": "0.0.3",
- "from": "crypto@0.0.x"
+ "version": "https://registry.npmjs.org/crypto/-/crypto-0.0.3.tgz",
+ "integrity": "sha1-RwqBuGvkxe4XrMggeh9TFa4g27A="
},
- "socket.io": {
- "version": "0.9.16",
- "from": "socket.io@0.9.x",
+ "dateformat": {
+ "version": "https://registry.npmjs.org/dateformat/-/dateformat-1.0.12.tgz",
+ "integrity": "sha1-nxJLZ1lMk3/3BpMuSmQsyo27/uk=",
+ "requires": {
+ "get-stdin": "4.0.1",
+ "meow": "3.7.0"
+ },
"dependencies": {
- "socket.io-client": {
- "version": "0.9.16",
- "from": "socket.io-client@0.9.16",
- "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-0.9.16.tgz",
+ "get-stdin": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-4.0.1.tgz",
+ "integrity": "sha1-uWjGsKBDhDJJAui/Gl3zJXmkUP4="
+ },
+ "meow": {
+ "version": "3.7.0",
+ "resolved": "https://registry.npmjs.org/meow/-/meow-3.7.0.tgz",
+ "integrity": "sha1-cstmi0JSKCkKu/qFaJJYcwioAfs=",
+ "requires": {
+ "camelcase-keys": "2.1.0",
+ "decamelize": "1.2.0",
+ "loud-rejection": "1.6.0",
+ "map-obj": "1.0.1",
+ "minimist": "1.2.0",
+ "normalize-package-data": "2.3.5",
+ "object-assign": "4.1.0",
+ "read-pkg-up": "1.0.1",
+ "redent": "1.0.0",
+ "trim-newlines": "1.0.0"
+ },
"dependencies": {
- "uglify-js": {
- "version": "1.2.5",
- "from": "uglify-js@1.2.5",
- "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-1.2.5.tgz"
+ "camelcase-keys": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-2.1.0.tgz",
+ "integrity": "sha1-MIvur/3ygRkFHvodkyITyRuPkuc=",
+ "requires": {
+ "camelcase": "2.1.1",
+ "map-obj": "1.0.1"
+ },
+ "dependencies": {
+ "camelcase": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-2.1.1.tgz",
+ "integrity": "sha1-fB0W1nmhu+WcoCys7PsBHiAfWh8="
+ }
+ }
},
- "ws": {
- "version": "0.4.31",
- "from": "ws@0.4.x",
+ "decamelize": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz",
+ "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA="
+ },
+ "loud-rejection": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/loud-rejection/-/loud-rejection-1.6.0.tgz",
+ "integrity": "sha1-W0b4AUft7leIcPCG0Eghz5mOVR8=",
+ "requires": {
+ "currently-unhandled": "0.4.1",
+ "signal-exit": "3.0.0"
+ },
"dependencies": {
- "commander": {
- "version": "0.6.1",
- "from": "commander@~0.6.1",
- "resolved": "https://registry.npmjs.org/commander/-/commander-0.6.1.tgz"
+ "currently-unhandled": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz",
+ "integrity": "sha1-mI3zP+qxke95mmE2nddsF635V+o=",
+ "requires": {
+ "array-find-index": "1.0.1"
+ },
+ "dependencies": {
+ "array-find-index": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/array-find-index/-/array-find-index-1.0.1.tgz",
+ "integrity": "sha1-C8Jd2slB7IpJauJY/UrBiAA+868="
+ }
+ }
},
- "nan": {
- "version": "0.3.2",
- "from": "nan@~0.3.0"
+ "signal-exit": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.0.tgz",
+ "integrity": "sha1-PAVDtl17T7xgts2UWT2b9DZzm+g="
+ }
+ }
+ },
+ "map-obj": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz",
+ "integrity": "sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0="
+ },
+ "minimist": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
+ "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ="
+ },
+ "normalize-package-data": {
+ "version": "2.3.5",
+ "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.3.5.tgz",
+ "integrity": "sha1-jZJPFClg4Xd+f/4XBUNjHMfLAt8=",
+ "requires": {
+ "hosted-git-info": "2.1.5",
+ "is-builtin-module": "1.0.0",
+ "semver": "5.3.0",
+ "validate-npm-package-license": "3.0.1"
+ },
+ "dependencies": {
+ "hosted-git-info": {
+ "version": "2.1.5",
+ "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.1.5.tgz",
+ "integrity": "sha1-C6gdkNouJas0ozLm7HeTbhWYEYs="
},
- "tinycolor": {
- "version": "0.0.1",
- "from": "tinycolor@0.x"
+ "is-builtin-module": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-1.0.0.tgz",
+ "integrity": "sha1-VAVy0096wxGfj3bDDLwbHgN6/74=",
+ "requires": {
+ "builtin-modules": "1.1.1"
+ },
+ "dependencies": {
+ "builtin-modules": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz",
+ "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8="
+ }
+ }
},
- "options": {
- "version": "0.0.5",
- "from": "options@>=0.0.5"
+ "semver": {
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-5.3.0.tgz",
+ "integrity": "sha1-myzl094C0XxgEq0yaqa00M9U+U8="
+ },
+ "validate-npm-package-license": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.1.tgz",
+ "integrity": "sha1-KAS6vnEq0zeUWaz74kdGqywwP7w=",
+ "requires": {
+ "spdx-correct": "1.0.2",
+ "spdx-expression-parse": "1.0.3"
+ },
+ "dependencies": {
+ "spdx-correct": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-1.0.2.tgz",
+ "integrity": "sha1-SzBz2TP/UfORLwOsVRlJikFQ20A=",
+ "requires": {
+ "spdx-license-ids": "1.2.2"
+ },
+ "dependencies": {
+ "spdx-license-ids": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-1.2.2.tgz",
+ "integrity": "sha1-yd96NCRZSt5r0RkA1ZZpbcBrrFc="
+ }
+ }
+ },
+ "spdx-expression-parse": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-1.0.3.tgz",
+ "integrity": "sha1-yjw4KMT+qKpEmXiEs5j8XWdDZEI="
+ }
+ }
}
}
},
- "xmlhttprequest": {
- "version": "1.4.2",
- "from": "xmlhttprequest@1.4.2",
- "resolved": "https://registry.npmjs.org/xmlhttprequest/-/xmlhttprequest-1.4.2.tgz"
+ "object-assign": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.0.tgz",
+ "integrity": "sha1-ejs9DpgGPUP0wD8uiubNUahog6A="
},
- "active-x-obfuscator": {
- "version": "0.0.1",
- "from": "active-x-obfuscator@0.0.1",
- "resolved": "https://registry.npmjs.org/active-x-obfuscator/-/active-x-obfuscator-0.0.1.tgz",
+ "read-pkg-up": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz",
+ "integrity": "sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI=",
+ "requires": {
+ "find-up": "1.1.2",
+ "read-pkg": "1.1.0"
+ },
"dependencies": {
- "zeparser": {
- "version": "0.0.5",
- "from": "zeparser@0.0.5",
- "resolved": "https://registry.npmjs.org/zeparser/-/zeparser-0.0.5.tgz"
+ "find-up": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz",
+ "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=",
+ "requires": {
+ "path-exists": "2.1.0",
+ "pinkie-promise": "2.0.1"
+ },
+ "dependencies": {
+ "path-exists": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz",
+ "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=",
+ "requires": {
+ "pinkie-promise": "2.0.1"
+ }
+ },
+ "pinkie-promise": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz",
+ "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=",
+ "requires": {
+ "pinkie": "2.0.4"
+ },
+ "dependencies": {
+ "pinkie": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz",
+ "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA="
+ }
+ }
+ }
+ }
+ },
+ "read-pkg": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz",
+ "integrity": "sha1-9f+qXs0pyzHAR0vKfXVra7KePyg=",
+ "requires": {
+ "load-json-file": "1.1.0",
+ "normalize-package-data": "2.3.5",
+ "path-type": "1.1.0"
+ },
+ "dependencies": {
+ "load-json-file": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz",
+ "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=",
+ "requires": {
+ "graceful-fs": "4.1.6",
+ "parse-json": "2.2.0",
+ "pify": "2.3.0",
+ "pinkie-promise": "2.0.1",
+ "strip-bom": "2.0.0"
+ },
+ "dependencies": {
+ "graceful-fs": {
+ "version": "4.1.6",
+ "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.6.tgz",
+ "integrity": "sha1-UUw4dysxvuLgi+3CGgrrOr9UwZ4="
+ },
+ "parse-json": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz",
+ "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=",
+ "requires": {
+ "error-ex": "1.3.0"
+ },
+ "dependencies": {
+ "error-ex": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.0.tgz",
+ "integrity": "sha1-5ntD8+gsluo6WE/+4Ln8MyXYAtk=",
+ "requires": {
+ "is-arrayish": "0.2.1"
+ },
+ "dependencies": {
+ "is-arrayish": {
+ "version": "0.2.1",
+ "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
+ "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0="
+ }
+ }
+ }
+ }
+ },
+ "pify": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
+ "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw="
+ },
+ "pinkie-promise": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz",
+ "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=",
+ "requires": {
+ "pinkie": "2.0.4"
+ },
+ "dependencies": {
+ "pinkie": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz",
+ "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA="
+ }
+ }
+ },
+ "strip-bom": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz",
+ "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=",
+ "requires": {
+ "is-utf8": "0.2.1"
+ },
+ "dependencies": {
+ "is-utf8": {
+ "version": "0.2.1",
+ "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz",
+ "integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI="
+ }
+ }
+ }
+ }
+ },
+ "path-type": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz",
+ "integrity": "sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=",
+ "requires": {
+ "graceful-fs": "4.1.6",
+ "pify": "2.3.0",
+ "pinkie-promise": "2.0.1"
+ },
+ "dependencies": {
+ "graceful-fs": {
+ "version": "4.1.6",
+ "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.6.tgz",
+ "integrity": "sha1-UUw4dysxvuLgi+3CGgrrOr9UwZ4="
+ },
+ "pify": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
+ "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw="
+ },
+ "pinkie-promise": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz",
+ "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=",
+ "requires": {
+ "pinkie": "2.0.4"
+ },
+ "dependencies": {
+ "pinkie": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz",
+ "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA="
+ }
+ }
+ }
+ }
+ }
+ }
}
}
+ },
+ "redent": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/redent/-/redent-1.0.0.tgz",
+ "integrity": "sha1-z5Fqsf1fHxbfsggi3W7H9zDCr94=",
+ "requires": {
+ "indent-string": "2.1.0",
+ "strip-indent": "1.0.1"
+ },
+ "dependencies": {
+ "indent-string": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-2.1.0.tgz",
+ "integrity": "sha1-ji1INIdCEhtKghi3oTfppSBJ3IA=",
+ "requires": {
+ "repeating": "2.0.1"
+ },
+ "dependencies": {
+ "repeating": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/repeating/-/repeating-2.0.1.tgz",
+ "integrity": "sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo=",
+ "requires": {
+ "is-finite": "1.0.1"
+ },
+ "dependencies": {
+ "is-finite": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.0.1.tgz",
+ "integrity": "sha1-ZDhgPq6+J5OUj/SkJi7I2z1iWXs=",
+ "requires": {
+ "number-is-nan": "1.0.0"
+ },
+ "dependencies": {
+ "number-is-nan": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.0.tgz",
+ "integrity": "sha1-wCD1KcUoKt/dIz2R1LGBw9aG3Es="
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "strip-indent": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-1.0.1.tgz",
+ "integrity": "sha1-DHlipq3vp7vUrDZkYKY4VSrhoKI=",
+ "requires": {
+ "get-stdin": "4.0.1"
+ }
+ }
+ }
+ },
+ "trim-newlines": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-1.0.0.tgz",
+ "integrity": "sha1-WIeWa7WCpFA6QetST301ARgVphM="
+ }
+ }
+ }
+ }
+ },
+ "debug": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
+ "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
+ "dev": true,
+ "requires": {
+ "ms": "2.0.0"
+ }
+ },
+ "dicer": {
+ "version": "0.2.5",
+ "resolved": "https://registry.npmjs.org/dicer/-/dicer-0.2.5.tgz",
+ "integrity": "sha1-WZbAhrszIYyBLAkL3cCc0S+stw8=",
+ "requires": {
+ "readable-stream": "1.1.14",
+ "streamsearch": "0.1.2"
+ },
+ "dependencies": {
+ "readable-stream": {
+ "version": "1.1.14",
+ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz",
+ "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=",
+ "requires": {
+ "core-util-is": "1.0.2",
+ "inherits": "2.0.3",
+ "isarray": "0.0.1",
+ "string_decoder": "0.10.31"
+ }
+ }
+ }
+ },
+ "diff": {
+ "version": "3.3.1",
+ "resolved": "https://registry.npmjs.org/diff/-/diff-3.3.1.tgz",
+ "integrity": "sha512-MKPHZDMB0o6yHyDryUOScqZibp914ksXwAMYMTHj6KO8UeKsRYNJD3oNCKjTqZon+V488P7N/HzXF8t7ZR95ww==",
+ "dev": true
+ },
+ "ee-first": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
+ "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0="
+ },
+ "end-of-stream": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.0.tgz",
+ "integrity": "sha1-epDYM+/abPpurA9JSduw+tOmMgY=",
+ "requires": {
+ "once": "1.4.0"
+ }
+ },
+ "escape-string-regexp": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
+ "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=",
+ "dev": true
+ },
+ "express": {
+ "version": "https://registry.npmjs.org/express/-/express-4.13.3.tgz",
+ "integrity": "sha1-3bLx+0UCvzNZjSsDKwN5YMpsgKM=",
+ "requires": {
+ "accepts": "1.2.13",
+ "array-flatten": "1.1.1",
+ "content-disposition": "0.5.0",
+ "content-type": "1.0.2",
+ "cookie": "0.1.3",
+ "cookie-signature": "1.0.6",
+ "debug": "2.2.0",
+ "depd": "1.0.1",
+ "escape-html": "1.0.2",
+ "etag": "1.7.0",
+ "finalhandler": "0.4.0",
+ "fresh": "0.3.0",
+ "merge-descriptors": "1.0.0",
+ "methods": "1.1.2",
+ "on-finished": "2.3.0",
+ "parseurl": "1.3.1",
+ "path-to-regexp": "0.1.7",
+ "proxy-addr": "1.0.10",
+ "qs": "4.0.0",
+ "range-parser": "1.0.3",
+ "send": "0.13.0",
+ "serve-static": "1.10.3",
+ "type-is": "1.6.13",
+ "utils-merge": "1.0.0",
+ "vary": "1.0.1"
+ },
+ "dependencies": {
+ "accepts": {
+ "version": "1.2.13",
+ "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.2.13.tgz",
+ "integrity": "sha1-5fHzkoxtlf2WVYw27D2dDeSm7Oo=",
+ "requires": {
+ "mime-types": "2.1.11",
+ "negotiator": "0.5.3"
+ },
+ "dependencies": {
+ "mime-types": {
+ "version": "2.1.11",
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.11.tgz",
+ "integrity": "sha1-wlnEcb2oCKhdbNGTtDCl+uRHOzw=",
+ "requires": {
+ "mime-db": "1.23.0"
+ },
+ "dependencies": {
+ "mime-db": {
+ "version": "1.23.0",
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.23.0.tgz",
+ "integrity": "sha1-oxtAcK2uon1zLqMzdApk0OyaZlk="
+ }
+ }
+ },
+ "negotiator": {
+ "version": "0.5.3",
+ "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.5.3.tgz",
+ "integrity": "sha1-Jp1cR2gQ7JLtvntsLygxY4T5p+g="
}
}
},
- "policyfile": {
- "version": "0.0.4",
- "from": "policyfile@0.0.4",
- "resolved": "https://registry.npmjs.org/policyfile/-/policyfile-0.0.4.tgz"
+ "array-flatten": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
+ "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI="
},
- "base64id": {
- "version": "0.1.0",
- "from": "base64id@0.1.0",
- "resolved": "https://registry.npmjs.org/base64id/-/base64id-0.1.0.tgz"
+ "content-disposition": {
+ "version": "0.5.0",
+ "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.0.tgz",
+ "integrity": "sha1-QoT+auBjCHRjnkToCkGMKTQTXp4="
},
- "redis": {
- "version": "0.7.3",
- "from": "redis@0.7.3",
- "resolved": "https://registry.npmjs.org/redis/-/redis-0.7.3.tgz"
+ "content-type": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.2.tgz",
+ "integrity": "sha1-t9ETrueo3Se9IRM8TcJSnfFyHu0="
+ },
+ "cookie": {
+ "version": "0.1.3",
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.1.3.tgz",
+ "integrity": "sha1-5zSlwUF/zkctWu+Cw4HKu2TRpDU="
+ },
+ "cookie-signature": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
+ "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw="
+ },
+ "debug": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz",
+ "integrity": "sha1-+HBX6ZWxofauaklgZkE3vFbwOdo=",
+ "requires": {
+ "ms": "0.7.1"
+ },
+ "dependencies": {
+ "ms": {
+ "version": "0.7.1",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.1.tgz",
+ "integrity": "sha1-nNE8A62/8ltl7/3nzoZO6VIBcJg="
+ }
+ }
+ },
+ "depd": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/depd/-/depd-1.0.1.tgz",
+ "integrity": "sha1-gK7GTJ1tl+ZcwqnKqTwKpqv3Oqo="
+ },
+ "escape-html": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.2.tgz",
+ "integrity": "sha1-130y+pjjjC9BroXpJ44ODmuhAiw="
+ },
+ "etag": {
+ "version": "1.7.0",
+ "resolved": "https://registry.npmjs.org/etag/-/etag-1.7.0.tgz",
+ "integrity": "sha1-A9MLX2fdbmMtKUXTDWZScxo01dg="
+ },
+ "finalhandler": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-0.4.0.tgz",
+ "integrity": "sha1-llpS2ejQXSuFdUhUH7ibU6JJfZs=",
+ "requires": {
+ "debug": "2.2.0",
+ "escape-html": "1.0.2",
+ "on-finished": "2.3.0",
+ "unpipe": "1.0.0"
+ },
+ "dependencies": {
+ "unpipe": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
+ "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw="
+ }
+ }
+ },
+ "fresh": {
+ "version": "0.3.0",
+ "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.3.0.tgz",
+ "integrity": "sha1-ZR+DjiJCTnVm3hYdg1jKoZn4PU8="
+ },
+ "merge-descriptors": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.0.tgz",
+ "integrity": "sha1-IWnPdTjhsMyH+4jhUC2EdLv3mGQ="
+ },
+ "methods": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
+ "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4="
+ },
+ "on-finished": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz",
+ "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=",
+ "requires": {
+ "ee-first": "1.1.1"
+ },
+ "dependencies": {
+ "ee-first": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
+ "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0="
+ }
+ }
+ },
+ "parseurl": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.1.tgz",
+ "integrity": "sha1-yKuMkiO6NIiKpkopeyiFO+wY2lY="
+ },
+ "path-to-regexp": {
+ "version": "0.1.7",
+ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz",
+ "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w="
+ },
+ "proxy-addr": {
+ "version": "1.0.10",
+ "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-1.0.10.tgz",
+ "integrity": "sha1-DUCoL4Afw1VWfS7LZe/j8HfxIcU=",
+ "requires": {
+ "forwarded": "0.1.0",
+ "ipaddr.js": "1.0.5"
+ },
+ "dependencies": {
+ "forwarded": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.0.tgz",
+ "integrity": "sha1-Ge+YdMSuHCl7zweP3mOgm2aoQ2M="
+ },
+ "ipaddr.js": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.0.5.tgz",
+ "integrity": "sha1-X6eM8wG4JceKvDBC2BJyMEnqI8c="
+ }
+ }
+ },
+ "qs": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/qs/-/qs-4.0.0.tgz",
+ "integrity": "sha1-wx2bdOwn33XlQ6hseHKO2NRiNgc="
+ },
+ "range-parser": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.0.3.tgz",
+ "integrity": "sha1-aHKCNTXGkuLCoBA4Jq/YLC4P8XU="
+ },
+ "send": {
+ "version": "0.13.0",
+ "resolved": "https://registry.npmjs.org/send/-/send-0.13.0.tgz",
+ "integrity": "sha1-UY+SGusFYK7H3KspkLFM9vPM5d4=",
+ "requires": {
+ "debug": "2.2.0",
+ "depd": "1.0.1",
+ "destroy": "1.0.3",
+ "escape-html": "1.0.2",
+ "etag": "1.7.0",
+ "fresh": "0.3.0",
+ "http-errors": "1.3.1",
+ "mime": "1.3.4",
+ "ms": "0.7.1",
+ "on-finished": "2.3.0",
+ "range-parser": "1.0.3",
+ "statuses": "1.2.1"
+ },
+ "dependencies": {
+ "destroy": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.3.tgz",
+ "integrity": "sha1-tDO0ck5x/YVR2YhRdIUcX8N34sk="
+ },
+ "http-errors": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.3.1.tgz",
+ "integrity": "sha1-GX4izevUGYWF6GlO9nhhl7ke2UI=",
+ "requires": {
+ "inherits": "2.0.1",
+ "statuses": "1.2.1"
+ },
+ "dependencies": {
+ "inherits": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz",
+ "integrity": "sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE="
+ }
+ }
+ },
+ "mime": {
+ "version": "1.3.4",
+ "resolved": "https://registry.npmjs.org/mime/-/mime-1.3.4.tgz",
+ "integrity": "sha1-EV+eO2s9rylZmDyzjxSaLUDrXVM="
+ },
+ "ms": {
+ "version": "0.7.1",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.1.tgz",
+ "integrity": "sha1-nNE8A62/8ltl7/3nzoZO6VIBcJg="
+ },
+ "statuses": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.2.1.tgz",
+ "integrity": "sha1-3e1FzBglbVHtQK7BQkidXGECbSg="
+ }
+ }
+ },
+ "serve-static": {
+ "version": "1.10.3",
+ "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.10.3.tgz",
+ "integrity": "sha1-zlpuzTEB/tXsCYJ9rCKpwpv7BTU=",
+ "requires": {
+ "escape-html": "1.0.3",
+ "parseurl": "1.3.1",
+ "send": "0.13.2"
+ },
+ "dependencies": {
+ "escape-html": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
+ "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg="
+ },
+ "send": {
+ "version": "0.13.2",
+ "resolved": "https://registry.npmjs.org/send/-/send-0.13.2.tgz",
+ "integrity": "sha1-dl52B8gFVFK7pvCwUllTUJhgNt4=",
+ "requires": {
+ "debug": "2.2.0",
+ "depd": "1.1.0",
+ "destroy": "1.0.4",
+ "escape-html": "1.0.3",
+ "etag": "1.7.0",
+ "fresh": "0.3.0",
+ "http-errors": "1.3.1",
+ "mime": "1.3.4",
+ "ms": "0.7.1",
+ "on-finished": "2.3.0",
+ "range-parser": "1.0.3",
+ "statuses": "1.2.1"
+ },
+ "dependencies": {
+ "depd": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.0.tgz",
+ "integrity": "sha1-4b2Cxqq2ztlluXuIsX7T5SjKGMM="
+ },
+ "destroy": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz",
+ "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA="
+ },
+ "http-errors": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.3.1.tgz",
+ "integrity": "sha1-GX4izevUGYWF6GlO9nhhl7ke2UI=",
+ "requires": {
+ "inherits": "2.0.1",
+ "statuses": "1.2.1"
+ },
+ "dependencies": {
+ "inherits": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz",
+ "integrity": "sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE="
+ }
+ }
+ },
+ "mime": {
+ "version": "1.3.4",
+ "resolved": "https://registry.npmjs.org/mime/-/mime-1.3.4.tgz",
+ "integrity": "sha1-EV+eO2s9rylZmDyzjxSaLUDrXVM="
+ },
+ "ms": {
+ "version": "0.7.1",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.1.tgz",
+ "integrity": "sha1-nNE8A62/8ltl7/3nzoZO6VIBcJg="
+ },
+ "statuses": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.2.1.tgz",
+ "integrity": "sha1-3e1FzBglbVHtQK7BQkidXGECbSg="
+ }
+ }
+ }
+ }
+ },
+ "type-is": {
+ "version": "1.6.13",
+ "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.13.tgz",
+ "integrity": "sha1-boO6e8MM0zp7sLf7AHN6IIW/nQg=",
+ "requires": {
+ "media-typer": "0.3.0",
+ "mime-types": "2.1.11"
+ },
+ "dependencies": {
+ "media-typer": {
+ "version": "0.3.0",
+ "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
+ "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g="
+ },
+ "mime-types": {
+ "version": "2.1.11",
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.11.tgz",
+ "integrity": "sha1-wlnEcb2oCKhdbNGTtDCl+uRHOzw=",
+ "requires": {
+ "mime-db": "1.23.0"
+ },
+ "dependencies": {
+ "mime-db": {
+ "version": "1.23.0",
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.23.0.tgz",
+ "integrity": "sha1-oxtAcK2uon1zLqMzdApk0OyaZlk="
+ }
+ }
+ }
+ }
+ },
+ "utils-merge": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.0.tgz",
+ "integrity": "sha1-ApT7kiu5N1FTVBxPcJYjHyh8ivg="
+ },
+ "vary": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/vary/-/vary-1.0.1.tgz",
+ "integrity": "sha1-meSYFWaihhGN+yuBc1ffeZM3bRA="
}
}
},
- "async": {
- "version": "0.2.9",
- "from": "async@0.2.x"
+ "finalhandler": {
+ "version": "0.5.1",
+ "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-0.5.1.tgz",
+ "integrity": "sha1-LEANjUUwk1vCMlScX6OF7Afeb80=",
+ "requires": {
+ "debug": "2.2.0",
+ "escape-html": "1.0.3",
+ "on-finished": "2.3.0",
+ "statuses": "1.3.1",
+ "unpipe": "1.0.0"
+ },
+ "dependencies": {
+ "debug": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz",
+ "integrity": "sha1-+HBX6ZWxofauaklgZkE3vFbwOdo=",
+ "requires": {
+ "ms": "0.7.1"
+ },
+ "dependencies": {
+ "ms": {
+ "version": "0.7.1",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.1.tgz",
+ "integrity": "sha1-nNE8A62/8ltl7/3nzoZO6VIBcJg="
+ }
+ }
+ },
+ "escape-html": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
+ "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg="
+ },
+ "on-finished": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz",
+ "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=",
+ "requires": {
+ "ee-first": "1.1.1"
+ },
+ "dependencies": {
+ "ee-first": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
+ "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0="
+ }
+ }
+ },
+ "statuses": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.3.1.tgz",
+ "integrity": "sha1-+vUbnrdKrvOzrPStX2Gr8ky3uT4="
+ },
+ "unpipe": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
+ "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw="
+ }
+ }
},
- "mime": {
- "version": "1.2.11",
- "from": "mime@1.2.x"
+ "flash": {
+ "version": "https://registry.npmjs.org/flash/-/flash-1.1.0.tgz",
+ "integrity": "sha1-irKyntO2ilz7Gom/mOf2nX8aAgo="
+ },
+ "formidable": {
+ "version": "http://registry.npmjs.org/formidable/-/formidable-1.0.17.tgz",
+ "integrity": "sha1-71SRSQ+UM7cF+qdyScmQKa40hVk="
+ },
+ "fs-extra": {
+ "version": "https://registry.npmjs.org/fs-extra/-/fs-extra-0.8.1.tgz",
+ "integrity": "sha1-Dld5/7/t9RG8dVWVx/A8BtS0Po0=",
+ "requires": {
+ "jsonfile": "1.1.1",
+ "mkdirp": "0.3.5",
+ "ncp": "0.4.2",
+ "rimraf": "2.2.8"
+ },
+ "dependencies": {
+ "mkdirp": {
+ "version": "0.3.5",
+ "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.3.5.tgz",
+ "integrity": "sha1-3j5fiWHIjHh+4TaN+EmsRBPsqNc="
+ }
+ }
+ },
+ "glob": {
+ "version": "https://registry.npmjs.org/glob/-/glob-7.0.3.tgz",
+ "integrity": "sha1-CqI1kxpKlqwT1g/6wvuHe9btT1g=",
+ "requires": {
+ "inflight": "1.0.5",
+ "inherits": "2.0.1",
+ "minimatch": "3.0.3",
+ "once": "1.3.3",
+ "path-is-absolute": "1.0.0"
+ },
+ "dependencies": {
+ "inflight": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.5.tgz",
+ "integrity": "sha1-2zIEzVqd4ubNiQuFxuL2a89PYgo=",
+ "requires": {
+ "once": "1.3.3",
+ "wrappy": "1.0.2"
+ },
+ "dependencies": {
+ "wrappy": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
+ "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8="
+ }
+ }
+ },
+ "inherits": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz",
+ "integrity": "sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE="
+ },
+ "minimatch": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.3.tgz",
+ "integrity": "sha1-Kk5AkLlrLbBqnX3wEFWmKnfJt3Q=",
+ "requires": {
+ "brace-expansion": "1.1.6"
+ },
+ "dependencies": {
+ "brace-expansion": {
+ "version": "1.1.6",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.6.tgz",
+ "integrity": "sha1-cZfX6qm4fmSDkOph/GbIRCdCDfk=",
+ "requires": {
+ "balanced-match": "0.4.2",
+ "concat-map": "0.0.1"
+ },
+ "dependencies": {
+ "balanced-match": {
+ "version": "0.4.2",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-0.4.2.tgz",
+ "integrity": "sha1-yz8+PHMtwPAe5wtAPzAuYddwmDg="
+ },
+ "concat-map": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
+ "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s="
+ }
+ }
+ }
+ }
+ },
+ "once": {
+ "version": "1.3.3",
+ "resolved": "https://registry.npmjs.org/once/-/once-1.3.3.tgz",
+ "integrity": "sha1-suJhVXzkwxTsgwTz+oJmPkKXyiA=",
+ "requires": {
+ "wrappy": "1.0.2"
+ },
+ "dependencies": {
+ "wrappy": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
+ "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8="
+ }
+ }
+ },
+ "path-is-absolute": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.0.tgz",
+ "integrity": "sha1-Jj2tpmqz8vsQv3+dJN2PPlcO+RI="
+ }
+ }
+ },
+ "growl": {
+ "version": "1.10.3",
+ "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.3.tgz",
+ "integrity": "sha512-hKlsbA5Vu3xsh1Cg3J7jSmX/WaW6A5oBeqzM88oNbCRQFz+zUaXm6yxS4RVytp1scBoJzSYl4YAEOQIt6O8V1Q==",
+ "dev": true
+ },
+ "has-flag": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-2.0.0.tgz",
+ "integrity": "sha1-6CB68cx7MNRGzHC3NLXovhj4jVE=",
+ "dev": true
+ },
+ "he": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz",
+ "integrity": "sha1-k0EP0hsAlzUVH4howvJx80J+I/0=",
+ "dev": true
+ },
+ "inherits": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
+ "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4="
+ },
+ "isarray": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz",
+ "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8="
},
"js-yaml": {
- "version": "2.1.3",
- "from": "js-yaml@2.1.x",
+ "version": "https://registry.npmjs.org/js-yaml/-/js-yaml-2.1.3.tgz",
+ "integrity": "sha1-D/tWF75VUlh4Bj16Fq7n/dKC6Ew=",
+ "requires": {
+ "argparse": "0.1.16",
+ "esprima": "1.0.4"
+ },
"dependencies": {
"argparse": {
- "version": "0.1.15",
- "from": "argparse@~ 0.1.11",
+ "version": "0.1.16",
+ "resolved": "https://registry.npmjs.org/argparse/-/argparse-0.1.16.tgz",
+ "integrity": "sha1-z9AeD7uj1srtBJ+9dY1A9lGW9Xw=",
+ "requires": {
+ "underscore": "1.7.0",
+ "underscore.string": "2.4.0"
+ },
"dependencies": {
"underscore": {
- "version": "1.4.4",
- "from": "underscore@~1.4.3"
+ "version": "1.7.0",
+ "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.7.0.tgz",
+ "integrity": "sha1-a7rwh3UA02vjTsqlhODbn+8DUgk="
},
"underscore.string": {
- "version": "2.3.3",
- "from": "underscore.string@~2.3.1"
+ "version": "2.4.0",
+ "resolved": "https://registry.npmjs.org/underscore.string/-/underscore.string-2.4.0.tgz",
+ "integrity": "sha1-jN2PusTi0uoefi6Al8QvRCKA+Fs="
}
}
},
"esprima": {
"version": "1.0.4",
- "from": "esprima@~ 1.0.2"
+ "resolved": "https://registry.npmjs.org/esprima/-/esprima-1.0.4.tgz",
+ "integrity": "sha1-n1V+CPw7TSbs6d00+Pv0drYlha0="
}
}
},
+ "jsonfile": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-1.1.1.tgz",
+ "integrity": "sha1-2k/WrXfxolUgPqY8e8Mtwx72RDM="
+ },
+ "lazystream": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/lazystream/-/lazystream-0.1.0.tgz",
+ "integrity": "sha1-GyXWPHcqTCDwpe0KnXf0hLbhaSA=",
+ "requires": {
+ "readable-stream": "1.0.34"
+ }
+ },
+ "lodash": {
+ "version": "https://registry.npmjs.org/lodash/-/lodash-3.10.1.tgz",
+ "integrity": "sha1-W/Rejkm6QYnhfUgnid/RW9FAt7Y="
+ },
+ "lru-cache": {
+ "version": "2.7.3",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.7.3.tgz",
+ "integrity": "sha1-bUUk6LlV+V1PW1iFHOId1y+06VI="
+ },
+ "media-typer": {
+ "version": "0.3.0",
+ "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
+ "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g="
+ },
+ "mime": {
+ "version": "https://registry.npmjs.org/mime/-/mime-1.2.11.tgz",
+ "integrity": "sha1-WCA+7Ybjpe8XrtK32evUfwpg3RA="
+ },
+ "mime-db": {
+ "version": "1.26.0",
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.26.0.tgz",
+ "integrity": "sha1-6v/NDk/Gk1z4E02iRuLmw1MFrf8="
+ },
+ "mime-types": {
+ "version": "2.1.14",
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.14.tgz",
+ "integrity": "sha1-9+99l1g/yvO30oK2+LVnnaselO4=",
+ "requires": {
+ "mime-db": "1.26.0"
+ }
+ },
+ "mkdirp": {
+ "version": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",
+ "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=",
+ "requires": {
+ "minimist": "0.0.8"
+ },
+ "dependencies": {
+ "minimist": {
+ "version": "0.0.8",
+ "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz",
+ "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0="
+ }
+ }
+ },
+ "mocha": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/mocha/-/mocha-4.1.0.tgz",
+ "integrity": "sha512-0RVnjg1HJsXY2YFDoTNzcc1NKhYuXKRrBAG2gDygmJJA136Cs2QlRliZG1mA0ap7cuaT30mw16luAeln+4RiNA==",
+ "dev": true,
+ "requires": {
+ "browser-stdout": "1.3.0",
+ "commander": "2.11.0",
+ "debug": "3.1.0",
+ "diff": "3.3.1",
+ "escape-string-regexp": "1.0.5",
+ "glob": "https://registry.npmjs.org/glob/-/glob-7.0.3.tgz",
+ "growl": "1.10.3",
+ "he": "1.1.1",
+ "mkdirp": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",
+ "supports-color": "4.4.0"
+ }
+ },
+ "ms": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+ "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
+ "dev": true
+ },
+ "multer": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/multer/-/multer-1.3.0.tgz",
+ "integrity": "sha1-CSsmcPaEb6SRSWXvyM+Uwg/sbNI=",
+ "requires": {
+ "append-field": "0.1.0",
+ "busboy": "0.2.14",
+ "concat-stream": "1.6.0",
+ "mkdirp": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",
+ "object-assign": "3.0.0",
+ "on-finished": "2.3.0",
+ "type-is": "1.6.14",
+ "xtend": "4.0.1"
+ }
+ },
+ "ncp": {
+ "version": "0.4.2",
+ "resolved": "https://registry.npmjs.org/ncp/-/ncp-0.4.2.tgz",
+ "integrity": "sha1-q8xsvT7C7Spyn/bnwfqPAXhKhXQ="
+ },
+ "object-assign": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-3.0.0.tgz",
+ "integrity": "sha1-m+3VygiXlJvKR+f/QIBi1Un1h/I="
+ },
+ "on-finished": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz",
+ "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=",
+ "requires": {
+ "ee-first": "1.1.1"
+ }
+ },
+ "once": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
+ "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
+ "requires": {
+ "wrappy": "1.0.2"
+ }
+ },
"optimist": {
- "version": "0.6.0",
- "from": "optimist@0.6.x",
+ "version": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz",
+ "integrity": "sha1-2j6nRob6IaGaERwybpDrFaAZZoY=",
+ "requires": {
+ "minimist": "0.0.10",
+ "wordwrap": "0.0.3"
+ },
"dependencies": {
+ "minimist": {
+ "version": "0.0.10",
+ "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz",
+ "integrity": "sha1-3j+YVD2/lggr5IrRoMfNqDYwHc8="
+ },
"wordwrap": {
- "version": "0.0.2",
- "from": "wordwrap@~0.0.2"
+ "version": "0.0.3",
+ "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz",
+ "integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc="
+ }
+ }
+ },
+ "passport": {
+ "version": "https://registry.npmjs.org/passport/-/passport-0.3.2.tgz",
+ "integrity": "sha1-ndAJ+RXo/glbASSgG4+C2gdRAQI=",
+ "requires": {
+ "passport-strategy": "1.0.0",
+ "pause": "0.0.1"
+ },
+ "dependencies": {
+ "passport-strategy": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/passport-strategy/-/passport-strategy-1.0.0.tgz",
+ "integrity": "sha1-tVOaqPwiWj0a0XlHbd8ja0QPUuQ="
},
- "minimist": {
- "version": "0.0.5",
- "from": "minimist@~0.0.1"
+ "pause": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/pause/-/pause-0.0.1.tgz",
+ "integrity": "sha1-HUCLP9t2kjuVQ9lvtMnf1TXZy10="
}
}
},
- "fs-extra": {
- "version": "0.8.1",
- "from": "fs-extra@0.8.x",
+ "passport-local": {
+ "version": "https://registry.npmjs.org/passport-local/-/passport-local-1.0.0.tgz",
+ "integrity": "sha1-H+YyaMkudWBmJkN+O5BmYsFbpu4=",
+ "requires": {
+ "passport-strategy": "1.0.0"
+ },
"dependencies": {
- "ncp": {
- "version": "0.4.2",
- "from": "ncp@~0.4.2"
+ "passport-strategy": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/passport-strategy/-/passport-strategy-1.0.0.tgz",
+ "integrity": "sha1-tVOaqPwiWj0a0XlHbd8ja0QPUuQ="
+ }
+ }
+ },
+ "process-nextick-args": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz",
+ "integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M="
+ },
+ "readable-stream": {
+ "version": "1.0.34",
+ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz",
+ "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=",
+ "requires": {
+ "core-util-is": "1.0.2",
+ "inherits": "2.0.3",
+ "isarray": "0.0.1",
+ "string_decoder": "0.10.31"
+ }
+ },
+ "request-promise": {
+ "version": "https://registry.npmjs.org/request-promise/-/request-promise-1.0.2.tgz",
+ "integrity": "sha1-FV9BBgjZJXwInB0LJvjY96iqhqE=",
+ "requires": {
+ "bluebird": "2.11.0",
+ "cls-bluebird": "1.1.3",
+ "lodash": "https://registry.npmjs.org/lodash/-/lodash-3.10.1.tgz",
+ "request": "2.74.0"
+ },
+ "dependencies": {
+ "bluebird": {
+ "version": "2.11.0",
+ "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-2.11.0.tgz",
+ "integrity": "sha1-U0uQM8AiyVecVro7Plpcqvu2UOE="
},
- "mkdirp": {
- "version": "0.3.5",
- "from": "mkdirp@0.3.x"
+ "cls-bluebird": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/cls-bluebird/-/cls-bluebird-1.1.3.tgz",
+ "integrity": "sha1-syY8EaCJsDlhhaG3q5BNkPAq1Cg=",
+ "requires": {
+ "is-bluebird": "1.0.1",
+ "shimmer": "1.1.0"
+ },
+ "dependencies": {
+ "is-bluebird": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/is-bluebird/-/is-bluebird-1.0.1.tgz",
+ "integrity": "sha1-wqBCwd5OnzafXFR7ZAzKln05ieo="
+ },
+ "shimmer": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/shimmer/-/shimmer-1.1.0.tgz",
+ "integrity": "sha1-l9c3cTf/u6tCVSLkKf4KqJpIizU="
+ }
+ }
},
- "jsonfile": {
- "version": "1.1.1",
- "from": "jsonfile@~1.1.0"
+ "request": {
+ "version": "2.74.0",
+ "resolved": "https://registry.npmjs.org/request/-/request-2.74.0.tgz",
+ "integrity": "sha1-dpPKdou7DqXIzgjAhKRe+gW4kqs=",
+ "requires": {
+ "aws-sign2": "0.6.0",
+ "aws4": "1.4.1",
+ "bl": "1.1.2",
+ "caseless": "0.11.0",
+ "combined-stream": "1.0.5",
+ "extend": "3.0.0",
+ "forever-agent": "0.6.1",
+ "form-data": "1.0.1",
+ "har-validator": "2.0.6",
+ "hawk": "3.1.3",
+ "http-signature": "1.1.1",
+ "is-typedarray": "1.0.0",
+ "isstream": "0.1.2",
+ "json-stringify-safe": "5.0.1",
+ "mime-types": "2.1.11",
+ "node-uuid": "1.4.7",
+ "oauth-sign": "0.8.2",
+ "qs": "6.2.1",
+ "stringstream": "0.0.5",
+ "tough-cookie": "2.3.1",
+ "tunnel-agent": "0.4.3"
+ },
+ "dependencies": {
+ "aws-sign2": {
+ "version": "0.6.0",
+ "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.6.0.tgz",
+ "integrity": "sha1-FDQt0428yU0OW4fXY81jYSwOeU8="
+ },
+ "aws4": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.4.1.tgz",
+ "integrity": "sha1-/efVKSRm0jDl7g9OA42d+qsI/GE="
+ },
+ "bl": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/bl/-/bl-1.1.2.tgz",
+ "integrity": "sha1-/cqHGplxOqANGeO7ukHER4emU5g=",
+ "requires": {
+ "readable-stream": "2.0.6"
+ },
+ "dependencies": {
+ "readable-stream": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.0.6.tgz",
+ "integrity": "sha1-j5A0HmilPMySh4jaz80Rs265t44=",
+ "requires": {
+ "core-util-is": "1.0.2",
+ "inherits": "2.0.1",
+ "isarray": "1.0.0",
+ "process-nextick-args": "1.0.7",
+ "string_decoder": "0.10.31",
+ "util-deprecate": "1.0.2"
+ },
+ "dependencies": {
+ "core-util-is": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
+ "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac="
+ },
+ "inherits": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz",
+ "integrity": "sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE="
+ },
+ "isarray": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
+ "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE="
+ },
+ "process-nextick-args": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz",
+ "integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M="
+ },
+ "string_decoder": {
+ "version": "0.10.31",
+ "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz",
+ "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ="
+ },
+ "util-deprecate": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
+ "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8="
+ }
+ }
+ }
+ }
+ },
+ "caseless": {
+ "version": "0.11.0",
+ "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.11.0.tgz",
+ "integrity": "sha1-cVuW6phBWTzDMGeSP17GDr2k99c="
+ },
+ "combined-stream": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.5.tgz",
+ "integrity": "sha1-k4NwpXtKUd6ix3wV1cX9+JUWQAk=",
+ "requires": {
+ "delayed-stream": "1.0.0"
+ },
+ "dependencies": {
+ "delayed-stream": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
+ "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk="
+ }
+ }
+ },
+ "extend": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.0.tgz",
+ "integrity": "sha1-WkdDU7nzNT3dgXbf03uRyDpG8dQ="
+ },
+ "forever-agent": {
+ "version": "0.6.1",
+ "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz",
+ "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE="
+ },
+ "form-data": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/form-data/-/form-data-1.0.1.tgz",
+ "integrity": "sha1-rjFduaSQf6BlUCMEpm13M0de43w=",
+ "requires": {
+ "async": "2.0.1",
+ "combined-stream": "1.0.5",
+ "mime-types": "2.1.11"
+ },
+ "dependencies": {
+ "async": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/async/-/async-2.0.1.tgz",
+ "integrity": "sha1-twnMAoCpw28J9FNr6CPIOKkEniU=",
+ "requires": {
+ "lodash": "4.15.0"
+ },
+ "dependencies": {
+ "lodash": {
+ "version": "4.15.0",
+ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.15.0.tgz",
+ "integrity": "sha1-MWI5HY8BQKoiz49rPDTWt/Y9Oqk="
+ }
+ }
+ }
+ }
+ },
+ "har-validator": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-2.0.6.tgz",
+ "integrity": "sha1-zcvAgYgmWtEZtqWnyKtw7s+10n0=",
+ "requires": {
+ "chalk": "1.1.3",
+ "commander": "2.9.0",
+ "is-my-json-valid": "2.13.1",
+ "pinkie-promise": "2.0.1"
+ },
+ "dependencies": {
+ "chalk": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
+ "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=",
+ "requires": {
+ "ansi-styles": "2.2.1",
+ "escape-string-regexp": "1.0.5",
+ "has-ansi": "2.0.0",
+ "strip-ansi": "3.0.1",
+ "supports-color": "2.0.0"
+ },
+ "dependencies": {
+ "ansi-styles": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz",
+ "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4="
+ },
+ "escape-string-regexp": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
+ "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ="
+ },
+ "has-ansi": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz",
+ "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=",
+ "requires": {
+ "ansi-regex": "2.0.0"
+ },
+ "dependencies": {
+ "ansi-regex": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.0.0.tgz",
+ "integrity": "sha1-xQYbbg74qBd15Q9dZhUb9r83EQc="
+ }
+ }
+ },
+ "strip-ansi": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
+ "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
+ "requires": {
+ "ansi-regex": "2.0.0"
+ },
+ "dependencies": {
+ "ansi-regex": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.0.0.tgz",
+ "integrity": "sha1-xQYbbg74qBd15Q9dZhUb9r83EQc="
+ }
+ }
+ },
+ "supports-color": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz",
+ "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc="
+ }
+ }
+ },
+ "commander": {
+ "version": "2.9.0",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-2.9.0.tgz",
+ "integrity": "sha1-nJkJQXbhIkDLItbFFGCYQA/g99Q=",
+ "requires": {
+ "graceful-readlink": "1.0.1"
+ },
+ "dependencies": {
+ "graceful-readlink": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/graceful-readlink/-/graceful-readlink-1.0.1.tgz",
+ "integrity": "sha1-TK+tdrxi8C+gObL5Tpo906ORpyU="
+ }
+ }
+ },
+ "is-my-json-valid": {
+ "version": "2.13.1",
+ "resolved": "https://registry.npmjs.org/is-my-json-valid/-/is-my-json-valid-2.13.1.tgz",
+ "integrity": "sha1-1Vd4qC/rawlj/0vhEdXRaE6JBwc=",
+ "requires": {
+ "generate-function": "2.0.0",
+ "generate-object-property": "1.2.0",
+ "jsonpointer": "2.0.0",
+ "xtend": "4.0.1"
+ },
+ "dependencies": {
+ "generate-function": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.0.0.tgz",
+ "integrity": "sha1-aFj+fAlpt9TpCTM3ZHrHn2DfvnQ="
+ },
+ "generate-object-property": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/generate-object-property/-/generate-object-property-1.2.0.tgz",
+ "integrity": "sha1-nA4cQDCM6AT0eDYYuTf6iPmdUNA=",
+ "requires": {
+ "is-property": "1.0.2"
+ },
+ "dependencies": {
+ "is-property": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz",
+ "integrity": "sha1-V/4cTkhHTt1lsJkR8msc1Ald2oQ="
+ }
+ }
+ },
+ "jsonpointer": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-2.0.0.tgz",
+ "integrity": "sha1-OvHdIP6FRjkQ1GmjheMwF9KgMNk="
+ },
+ "xtend": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz",
+ "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68="
+ }
+ }
+ },
+ "pinkie-promise": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz",
+ "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=",
+ "requires": {
+ "pinkie": "2.0.4"
+ },
+ "dependencies": {
+ "pinkie": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz",
+ "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA="
+ }
+ }
+ }
+ }
+ },
+ "hawk": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/hawk/-/hawk-3.1.3.tgz",
+ "integrity": "sha1-B4REvXwWQLD+VA0sm3PVlnjo4cQ=",
+ "requires": {
+ "boom": "2.10.1",
+ "cryptiles": "2.0.5",
+ "hoek": "2.16.3",
+ "sntp": "1.0.9"
+ },
+ "dependencies": {
+ "boom": {
+ "version": "2.10.1",
+ "resolved": "https://registry.npmjs.org/boom/-/boom-2.10.1.tgz",
+ "integrity": "sha1-OciRjO/1eZ+D+UkqhI9iWt0Mdm8=",
+ "requires": {
+ "hoek": "2.16.3"
+ }
+ },
+ "cryptiles": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-2.0.5.tgz",
+ "integrity": "sha1-O9/s3GCBR8HGcgL6KR59ylnqo7g=",
+ "requires": {
+ "boom": "2.10.1"
+ }
+ },
+ "hoek": {
+ "version": "2.16.3",
+ "resolved": "https://registry.npmjs.org/hoek/-/hoek-2.16.3.tgz",
+ "integrity": "sha1-ILt0A9POo5jpHcRxCo/xuCdKJe0="
+ },
+ "sntp": {
+ "version": "1.0.9",
+ "resolved": "https://registry.npmjs.org/sntp/-/sntp-1.0.9.tgz",
+ "integrity": "sha1-ZUEYTMkK7qbG57NeJlkIJEPGYZg=",
+ "requires": {
+ "hoek": "2.16.3"
+ }
+ }
+ }
+ },
+ "http-signature": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.1.1.tgz",
+ "integrity": "sha1-33LiZwZs0Kxn+3at+OE0qPvPkb8=",
+ "requires": {
+ "assert-plus": "0.2.0",
+ "jsprim": "1.3.0",
+ "sshpk": "1.10.0"
+ },
+ "dependencies": {
+ "assert-plus": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-0.2.0.tgz",
+ "integrity": "sha1-104bh+ev/A24qttwIfP+SBAasjQ="
+ },
+ "jsprim": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.3.0.tgz",
+ "integrity": "sha1-zi4b74NSBLTzCZkoxgL4tq5hVlA=",
+ "requires": {
+ "extsprintf": "1.0.2",
+ "json-schema": "0.2.2",
+ "verror": "1.3.6"
+ },
+ "dependencies": {
+ "extsprintf": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.0.2.tgz",
+ "integrity": "sha1-4QgOBljjALBilJkMxw4VAiNf1VA="
+ },
+ "json-schema": {
+ "version": "0.2.2",
+ "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.2.tgz",
+ "integrity": "sha1-UDVPGfYDkXxpX3C4Wvp3w7DyNQY="
+ },
+ "verror": {
+ "version": "1.3.6",
+ "resolved": "https://registry.npmjs.org/verror/-/verror-1.3.6.tgz",
+ "integrity": "sha1-z/XfEpRtKX0rqu+qJoniW+AcAFw=",
+ "requires": {
+ "extsprintf": "1.0.2"
+ }
+ }
+ }
+ },
+ "sshpk": {
+ "version": "1.10.0",
+ "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.10.0.tgz",
+ "integrity": "sha1-EE1roq+yrAmauVZ8DRk5d/Kcbfo=",
+ "requires": {
+ "asn1": "0.2.3",
+ "assert-plus": "1.0.0",
+ "bcrypt-pbkdf": "1.0.0",
+ "dashdash": "1.14.0",
+ "ecc-jsbn": "0.1.1",
+ "getpass": "0.1.6",
+ "jodid25519": "1.0.2",
+ "jsbn": "0.1.0",
+ "tweetnacl": "0.13.3"
+ },
+ "dependencies": {
+ "asn1": {
+ "version": "0.2.3",
+ "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.3.tgz",
+ "integrity": "sha1-2sh4dxPJlmhJ/IGAd36+nB3fO4Y="
+ },
+ "assert-plus": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz",
+ "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU="
+ },
+ "bcrypt-pbkdf": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.0.tgz",
+ "integrity": "sha1-PKdrhSQccXC/fZcD57mqdGMAQNQ=",
+ "optional": true,
+ "requires": {
+ "tweetnacl": "0.14.3"
+ },
+ "dependencies": {
+ "tweetnacl": {
+ "version": "0.14.3",
+ "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.3.tgz",
+ "integrity": "sha1-PaOC9nDyXe1417PReSEZvKC3Ey0=",
+ "optional": true
+ }
+ }
+ },
+ "dashdash": {
+ "version": "1.14.0",
+ "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.0.tgz",
+ "integrity": "sha1-KeSGxUGL8PNWA0qZPVFoajPoQUE=",
+ "requires": {
+ "assert-plus": "1.0.0"
+ }
+ },
+ "ecc-jsbn": {
+ "version": "0.1.1",
+ "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz",
+ "integrity": "sha1-D8c6ntXw1Tw4GTOYUj735UN3dQU=",
+ "optional": true,
+ "requires": {
+ "jsbn": "0.1.0"
+ }
+ },
+ "getpass": {
+ "version": "0.1.6",
+ "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.6.tgz",
+ "integrity": "sha1-KD/9n8ElaECHUxHBtg6MQBhxEOY=",
+ "requires": {
+ "assert-plus": "1.0.0"
+ }
+ },
+ "jodid25519": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/jodid25519/-/jodid25519-1.0.2.tgz",
+ "integrity": "sha1-BtSRIlUJNBlHfUJWM2BuDpB4KWc=",
+ "optional": true,
+ "requires": {
+ "jsbn": "0.1.0"
+ }
+ },
+ "jsbn": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.0.tgz",
+ "integrity": "sha1-ZQmH2g3XT06/WhE3eiqi0nPpff0=",
+ "optional": true
+ },
+ "tweetnacl": {
+ "version": "0.13.3",
+ "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.13.3.tgz",
+ "integrity": "sha1-1ii1bzvMPVrnS6nUwacE3vWrS1Y=",
+ "optional": true
+ }
+ }
+ }
+ }
+ },
+ "is-typedarray": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz",
+ "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo="
+ },
+ "isstream": {
+ "version": "0.1.2",
+ "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz",
+ "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo="
+ },
+ "json-stringify-safe": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz",
+ "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus="
+ },
+ "mime-types": {
+ "version": "2.1.11",
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.11.tgz",
+ "integrity": "sha1-wlnEcb2oCKhdbNGTtDCl+uRHOzw=",
+ "requires": {
+ "mime-db": "1.23.0"
+ },
+ "dependencies": {
+ "mime-db": {
+ "version": "1.23.0",
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.23.0.tgz",
+ "integrity": "sha1-oxtAcK2uon1zLqMzdApk0OyaZlk="
+ }
+ }
+ },
+ "node-uuid": {
+ "version": "1.4.7",
+ "resolved": "https://registry.npmjs.org/node-uuid/-/node-uuid-1.4.7.tgz",
+ "integrity": "sha1-baWhdmjEs91ZYjvaEc9/pMH2Cm8="
+ },
+ "oauth-sign": {
+ "version": "0.8.2",
+ "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz",
+ "integrity": "sha1-Rqarfwrq2N6unsBWV4C31O/rnUM="
+ },
+ "qs": {
+ "version": "6.2.1",
+ "resolved": "https://registry.npmjs.org/qs/-/qs-6.2.1.tgz",
+ "integrity": "sha1-zgPF/wk1vB2daanxTL0Y5WjWdiU="
+ },
+ "stringstream": {
+ "version": "0.0.5",
+ "resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.5.tgz",
+ "integrity": "sha1-TkhM1N5aC7vuGORjB3EKioFiGHg="
+ },
+ "tough-cookie": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.1.tgz",
+ "integrity": "sha1-mcd9+7fYBCSeiimdTLD9gf7wg/0="
+ },
+ "tunnel-agent": {
+ "version": "0.4.3",
+ "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.4.3.tgz",
+ "integrity": "sha1-Y3PbdpCf5XDgjXNYM2Xtgop07us="
+ }
+ }
+ }
+ }
+ },
+ "rimraf": {
+ "version": "2.2.8",
+ "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.2.8.tgz",
+ "integrity": "sha1-5Dm+Kq7jJzIZUnMPmaiSnk/FBYI="
+ },
+ "should": {
+ "version": "13.2.1",
+ "resolved": "https://registry.npmjs.org/should/-/should-13.2.1.tgz",
+ "integrity": "sha512-l+/NwEMO+DcstsHEwPHRHzC9j4UOE3VQwJGcMWSsD/vqpqHbnQ+1iSHy64Ihmmjx1uiRPD9pFadTSc3MJtXAgw==",
+ "dev": true,
+ "requires": {
+ "should-equal": "2.0.0",
+ "should-format": "3.0.3",
+ "should-type": "1.4.0",
+ "should-type-adaptors": "1.1.0",
+ "should-util": "1.0.0"
+ }
+ },
+ "should-equal": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/should-equal/-/should-equal-2.0.0.tgz",
+ "integrity": "sha512-ZP36TMrK9euEuWQYBig9W55WPC7uo37qzAEmbjHz4gfyuXrEUgF8cUvQVO+w+d3OMfPvSRQJ22lSm8MQJ43LTA==",
+ "dev": true,
+ "requires": {
+ "should-type": "1.4.0"
+ }
+ },
+ "should-format": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/should-format/-/should-format-3.0.3.tgz",
+ "integrity": "sha1-m/yPdPo5IFxT04w01xcwPidxJPE=",
+ "dev": true,
+ "requires": {
+ "should-type": "1.4.0",
+ "should-type-adaptors": "1.1.0"
+ }
+ },
+ "should-type": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/should-type/-/should-type-1.4.0.tgz",
+ "integrity": "sha1-B1bYzoRt/QmEOmlHcZ36DUz/XPM=",
+ "dev": true
+ },
+ "should-type-adaptors": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/should-type-adaptors/-/should-type-adaptors-1.1.0.tgz",
+ "integrity": "sha512-JA4hdoLnN+kebEp2Vs8eBe9g7uy0zbRo+RMcU0EsNy+R+k049Ki+N5tT5Jagst2g7EAja+euFuoXFCa8vIklfA==",
+ "dev": true,
+ "requires": {
+ "should-type": "1.4.0",
+ "should-util": "1.0.0"
+ }
+ },
+ "should-util": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/should-util/-/should-util-1.0.0.tgz",
+ "integrity": "sha1-yYzaN0qmsZDfi6h8mInCtNtiAGM=",
+ "dev": true
+ },
+ "sigmund": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/sigmund/-/sigmund-1.0.1.tgz",
+ "integrity": "sha1-P/IfGYytIXX587eBhT/ZTQ0ZtZA="
+ },
+ "socket.io": {
+ "version": "https://registry.npmjs.org/socket.io/-/socket.io-0.9.17.tgz",
+ "integrity": "sha512-jRHmXZUIycXQv1BStfM1ogvcVZdkI4Z7v/ZgxQzhHYw9SYT++UbOyzantuR7CHUfBhLJQrH92pZuQ9TqGvXw3A==",
+ "requires": {
+ "base64id": "0.1.0",
+ "policyfile": "0.0.4",
+ "redis": "0.7.3",
+ "socket.io-client": "0.9.16"
+ },
+ "dependencies": {
+ "base64id": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/base64id/-/base64id-0.1.0.tgz",
+ "integrity": "sha1-As4P3u4M709ACA4ec+g08LG/zj8="
+ },
+ "policyfile": {
+ "version": "0.0.4",
+ "resolved": "https://registry.npmjs.org/policyfile/-/policyfile-0.0.4.tgz",
+ "integrity": "sha1-1rgurZiueeviKOLa9ZAzEeyYLk0="
+ },
+ "redis": {
+ "version": "0.7.3",
+ "resolved": "https://registry.npmjs.org/redis/-/redis-0.7.3.tgz",
+ "integrity": "sha1-7le3pE0l7BWU5ENl2BZfp9HUgRo=",
+ "optional": true
},
- "rimraf": {
- "version": "2.2.5",
- "from": "rimraf@~2.2.0"
+ "socket.io-client": {
+ "version": "0.9.16",
+ "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-0.9.16.tgz",
+ "integrity": "sha1-TadRXF53MEHRtCOXBBW8xDDzX8Y=",
+ "requires": {
+ "active-x-obfuscator": "0.0.1",
+ "uglify-js": "1.2.5",
+ "ws": "0.4.32",
+ "xmlhttprequest": "1.4.2"
+ },
+ "dependencies": {
+ "active-x-obfuscator": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/active-x-obfuscator/-/active-x-obfuscator-0.0.1.tgz",
+ "integrity": "sha1-CJuJs3FF/x2ex0r2UwvlUmyuHxo=",
+ "requires": {
+ "zeparser": "0.0.5"
+ },
+ "dependencies": {
+ "zeparser": {
+ "version": "0.0.5",
+ "resolved": "https://registry.npmjs.org/zeparser/-/zeparser-0.0.5.tgz",
+ "integrity": "sha1-A3JlYbwmjy5URPVMZlt/1KjAKeI="
+ }
+ }
+ },
+ "uglify-js": {
+ "version": "1.2.5",
+ "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-1.2.5.tgz",
+ "integrity": "sha1-tULCx29477NLIAsgF3Y0Mw/3ArY="
+ },
+ "ws": {
+ "version": "0.4.32",
+ "resolved": "https://registry.npmjs.org/ws/-/ws-0.4.32.tgz",
+ "integrity": "sha1-eHphVEFPPJntg8V3IVOyD+sM7DI=",
+ "requires": {
+ "commander": "2.1.0",
+ "nan": "1.0.0",
+ "options": "0.0.6",
+ "tinycolor": "0.0.1"
+ },
+ "dependencies": {
+ "commander": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-2.1.0.tgz",
+ "integrity": "sha1-0SG7roYNmZKj1Re6lvVliOR8Z4E="
+ },
+ "nan": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/nan/-/nan-1.0.0.tgz",
+ "integrity": "sha1-riT4hQgY1mL8q1rPfzuVv6oszzg="
+ },
+ "options": {
+ "version": "0.0.6",
+ "resolved": "https://registry.npmjs.org/options/-/options-0.0.6.tgz",
+ "integrity": "sha1-7CLTEoBrtT5zF3Pnza788cZDEo8="
+ },
+ "tinycolor": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/tinycolor/-/tinycolor-0.0.1.tgz",
+ "integrity": "sha1-MgtaUtg6u1l42Bo+iH1K77FaYWQ="
+ }
+ }
+ },
+ "xmlhttprequest": {
+ "version": "1.4.2",
+ "resolved": "https://registry.npmjs.org/xmlhttprequest/-/xmlhttprequest-1.4.2.tgz",
+ "integrity": "sha1-AUU6HZvtHo8XL2SVu/TIxCYyFQA="
+ }
+ }
+ }
+ }
+ },
+ "streamsearch": {
+ "version": "0.1.2",
+ "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-0.1.2.tgz",
+ "integrity": "sha1-gIudDlb8Jz2Am6VzOOkpkZoanxo="
+ },
+ "string_decoder": {
+ "version": "0.10.31",
+ "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz",
+ "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ="
+ },
+ "supports-color": {
+ "version": "4.4.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-4.4.0.tgz",
+ "integrity": "sha512-rKC3+DyXWgK0ZLKwmRsrkyHVZAjNkfzeehuFWdGGcqGDTZFH73+RH6S/RDAAxl9GusSjZSUWYLmT9N5pzXFOXQ==",
+ "dev": true,
+ "requires": {
+ "has-flag": "2.0.0"
+ }
+ },
+ "tar-stream": {
+ "version": "0.4.7",
+ "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-0.4.7.tgz",
+ "integrity": "sha1-Hx0s6evHtCdlJDyg6PG3v9oKrc0=",
+ "requires": {
+ "bl": "0.9.5",
+ "end-of-stream": "1.4.0",
+ "readable-stream": "1.0.34",
+ "xtend": "4.0.1"
+ }
+ },
+ "type-is": {
+ "version": "1.6.14",
+ "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.14.tgz",
+ "integrity": "sha1-4hljnBfe0coHiQkt1UoDgmuBfLI=",
+ "requires": {
+ "media-typer": "0.3.0",
+ "mime-types": "2.1.14"
+ }
+ },
+ "typedarray": {
+ "version": "0.0.6",
+ "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz",
+ "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c="
+ },
+ "util": {
+ "version": "http://registry.npmjs.org/util/-/util-0.10.3.tgz",
+ "integrity": "sha1-evsa/lCAUkZInj23/g7TeTNqwPk=",
+ "requires": {
+ "inherits": "2.0.1"
+ },
+ "dependencies": {
+ "inherits": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz",
+ "integrity": "sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE="
+ }
+ }
+ },
+ "util-deprecate": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
+ "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8="
+ },
+ "wrappy": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
+ "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8="
+ },
+ "xtend": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz",
+ "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68="
+ },
+ "zip-folder": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/zip-folder/-/zip-folder-1.0.0.tgz",
+ "integrity": "sha1-cKd0T9F4mi/rQa00GbMun9h5V7I=",
+ "requires": {
+ "archiver": "0.11.0"
+ }
+ },
+ "zip-stream": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-0.4.1.tgz",
+ "integrity": "sha1-TqeVqM4Z6fq0mjHR0IdyFBWfA6M=",
+ "requires": {
+ "compress-commons": "0.1.6",
+ "lodash": "2.4.2",
+ "readable-stream": "1.0.34"
+ },
+ "dependencies": {
+ "lodash": {
+ "version": "2.4.2",
+ "resolved": "https://registry.npmjs.org/lodash/-/lodash-2.4.2.tgz",
+ "integrity": "sha1-+t2DS5aDBz2hebPq5tnA0VBT9z4="
}
}
}
diff --git a/package.json b/package.json
index 5e359fae9..382331f5e 100644
--- a/package.json
+++ b/package.json
@@ -5,7 +5,8 @@
"author": "VWF ",
"scripts": {
"start": "node ./node-server.js --applicationPath=./public",
- "debug": "node --debug ./node-server.js --applicationPath=./public",
+ "debug": "node --inspect ./node-server.js --applicationPath=./public",
+ "prepublish": "cd support/lobby && npm install",
"test": "echo \"Error: no test specified\" && exit 1"
},
"directories": {
@@ -18,23 +19,39 @@
"url": "https://github.com/virtual-world-framework/vwf.git"
},
"dependencies": {
- "crypto": "0.0.x",
- "socket.io": "0.9.x",
+ "adm-zip": "^0.4.7",
+ "amdefine": "^1.0.1",
+ "amqplib": "^0.5.1",
"async": "0.2.x",
- "mime": "1.2.x",
+ "bluebird": "^3.0.5",
+ "body-parser": "^1.14.1",
+ "config": "^1.16.0",
+ "cookie-parser": "^1.4.0",
+ "cookie-session": "^2.0.0-alpha.1",
+ "crypto": "0.0.x",
+ "dateformat": "^1.0.11",
+ "express": "^4.13.3",
+ "finalhandler": "^0.5.1",
+ "flash": "^1.1.0",
+ "formidable": "1.0.x",
+ "fs-extra": "0.8.x",
+ "glob": "^7.0.3",
"js-yaml": "2.1.x",
+ "lodash": "^3.10.1",
+ "mime": "1.2.x",
+ "mkdirp": "^0.5.1",
+ "multer": "^1.3.0",
"optimist": "0.6.x",
- "fs-extra": "0.8.x"
+ "passport": "^0.3.2",
+ "passport-local": "^1.0.0",
+ "request-promise": "^1.0.2",
+ "socket.io": "0.9.x",
+ "util": "0.10.x",
+ "zip-folder": "^1.0.0"
},
"devDependencies": {
- "node-inspector": "x.x.x",
- "nodebug": "x.x.x",
"mocha": "x.x.x",
"should": "x.x.x"
},
- "license": {
- "type": "Apache",
- "url": "http://www.apache.org/licenses/LICENSE-2.0.html"
- }
+ "license": "Apache-2.0"
}
-
diff --git a/public/ONR_Logo.jpg b/public/ONR_Logo.jpg
new file mode 100644
index 000000000..97f9c4efc
Binary files /dev/null and b/public/ONR_Logo.jpg differ
diff --git a/public/favicon.ico b/public/favicon.ico
new file mode 100644
index 000000000..6c8d8cb0e
Binary files /dev/null and b/public/favicon.ico differ
diff --git a/public/itdg b/public/itdg
new file mode 160000
index 000000000..cd979d790
--- /dev/null
+++ b/public/itdg
@@ -0,0 +1 @@
+Subproject commit cd979d7902a76d6248be69b100ca41a9d10aa065
diff --git a/support/client/bin/r.js b/support/client/bin/r.js
index f4547f073..33c7193ee 100644
--- a/support/client/bin/r.js
+++ b/support/client/bin/r.js
@@ -440,7 +440,7 @@ var requirejs, require, define, xpcUtil;
//Defaults. Do not set a default for map
//config to speed up normalize(), which
//will run faster if there is no default.
- waitSeconds: 7,
+ waitSeconds: 0,
baseUrl: './',
paths: {},
bundles: {},
diff --git a/support/client/lib/ITDG/version.js b/support/client/lib/ITDG/version.js
new file mode 100644
index 000000000..436001d47
--- /dev/null
+++ b/support/client/lib/ITDG/version.js
@@ -0,0 +1,42 @@
+// Copyright 2012-14 United States Government, as represented by the Secretary of Defense, Under
+// Secretary of Defense (Personnel & Readiness).
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+// in compliance with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software distributed under the License
+// is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+// or implied. See the License for the specific language governing permissions and limitations under
+// the License.
+
+
+
+/// The version identifier has the following form:
+///
+/// major, minor, patch, release, build, derivative [ app, major, minor, patch ]
+///
+/// Fields are defined according to [SemVer](http://semver.org). The `release` and `build`
+/// fields are optional, but they are given low-precedence values by default so that official
+/// builds will always have a higher precedence than unofficial builds. The build tool removes
+/// these fields when appropriate.
+///
+/// The build tool overwrites the version identifier on the following line, and it isn't
+/// particuarly clever about it. Take care to keep the comment and formatting intact when
+/// bumping the version number.
+
+var version = [ 0, 8, 0, "", "", [ "ITDG", 3, 2, 2 ] ]; // version-identifier
+
+/// Render the version identifier as a SemVer-style string.
+
+version.toString = function() {
+ return this.slice( 0, 3 ).join( "." ) +
+ ( this[ 5 ] ? "-" + this[ 5 ][0] + "." + this[ 5 ].slice( 1, 4 ).join( "." ) : "" ) +
+ ( this[ 3 ] ? "-" + this[ 3 ] : "" ) +
+ ( this[ 4 ] ? "+" + this[ 4 ] : "" );
+};
+
+version.getDerivativeVersion = function () {
+ return ( this[ 5 ] ? this[ 5 ][0] + " " + this[ 5 ].slice( 1, 4 ).join( "." ) : "" );
+}
\ No newline at end of file
diff --git a/support/client/lib/index.css b/support/client/lib/index.css
index 63c1ce234..bd27cd4a0 100644
--- a/support/client/lib/index.css
+++ b/support/client/lib/index.css
@@ -20,10 +20,6 @@ form {
margin: 2em;
}
-label {
- font-size: 18px;
-}
-
input {
font-size: 18px;
padding: 5px;
diff --git a/support/client/lib/index.html b/support/client/lib/index.html
index 511f62048..520100ccb 100644
--- a/support/client/lib/index.html
+++ b/support/client/lib/index.html
@@ -25,37 +25,36 @@
Virtual World Framework
-
+
-
+
+
-
-
+
-
+
+
-
+
-
-
-
-
+
-
-
+
+
-
-
+
+
-
-
-
+
+
+
+
-
+
@@ -75,14 +74,13 @@
var match;
var application;
var regex = /([^&=]+)=?([^&]*)/g;
-
var requireConfig = {
paths: {
jquery: "jquery-1.10.2.min",
"jquery-ui": "jquery-ui-1.10.3.custom.min",
hammer: "jquery.hammer",
},
- shim: {
+ shim: {
"jquery-ui": {
deps: ["jquery"],
exports: "$"
@@ -92,9 +90,12 @@
deps: ["jquery"],
exports: "Hammer"
}
- }
+ },
+ // used to bust the cache, for development use (new Date()).getTime()
+ urlArgs: "v="+version.toString()
};
require(requireConfig, ["jquery", "jquery-encoder-0.1.0"], function($) {
+
while(match = regex.exec(queryString)) {
var key = $.encoder.canonicalize(match[1]);
var parameters = $.encoder.canonicalize(match[2]);
@@ -130,6 +131,7 @@
vwf.loadConfiguration(application, userLibraries, compatibilityCheck);
});
+
@@ -137,13 +139,11 @@
-
-
-
+
@@ -159,7 +159,7 @@
-
+