<commit_before>const ByteBuffer = require('bytebuffer');
const CRC32 = require('buffer-crc32');
const SteamCrypto = require('@doctormckay/steam-crypto');
const SteamID = require('steamid');

const Helpers = require('./helpers.js');
const Schema = require('./protobufs.js');
const SteamUser = require('../index.js');

/**
 * Request an encrypted appticket for a particular app. The app must be set up on the Steam backend for encrypted apptickets.
 * @param {int} appid - The Steam AppID of the app you want a ticket for
 * @param {Buffer} [userData] - If the app expects some "user data", provide it here
 * @param {function} callback - First argument is "err", second is the ticket as a Buffer (on success)
 */
SteamUser.prototype.getEncryptedAppTicket = function(appid, userData, callback) {
	if (typeof userData === 'function') {
		callback = userData;
		userData = new Buffer(0);
	}

	this._send(SteamUser.EMsg.ClientRequestEncryptedAppTicket, {"app_id": appid, "userdata": userData}, (body) => {
		if (body.eresult != SteamUser.EResult.OK) {
			callback(Helpers.eresultError(body.eresult));
			return;
		}

		if (body.app_id != appid) {
			// Don't know if this can even happen
			callback(new Error("Steam did not send an appticket for the requested appid"));
			return;
		}

		if (!body.encrypted_app_ticket) {
			callback(new Error("No appticket in response"));
			return;
		}

		callback(null, (new Schema.EncryptedAppTicket(body.encrypted_app_ticket)).toBuffer());
	});
};

/**
 *
 * @param {Buffer} ticket - The raw encrypted appticket
 * @param {Buffer|string} encryptionKey - The app's encryption key, either raw hex or a Buffer
 * @returns {object}
 */
SteamUser.parseEncryptedAppTicket = function(ticket, encryptionKey) {
	try {
		let outer = Schema.EncryptedAppTicket.decode(ticket);
		let key = typeof encryptionKey === 'string' ? Buffer.from(encryptionKey, 'hex') : encryptionKey;
		let decrypted = SteamCrypto.symmetricDecrypt(outer.encrypted_ticket.toBuffer(), key);

		if (CRC32.unsigned(decrypted) != outer.crc_encryptedticket) {
			return null;
		}

		// the beginning is the user-supplied data
		let userData = decrypted.slice(0, outer.cb_encrypteduserdata);
		let ownershipTicketLength = decrypted.readUInt32LE(outer.cb_encrypteduserdata);
		let ownershipTicket = SteamUser.parseAppTicket(decrypted.slice(outer.cb_encrypteduserdata, outer.cb_encrypteduserdata + ownershipTicketLength));
		// there are 32 more bytes but I dunno what they're for
		if (ownershipTicket) {
			ownershipTicket.userData = userData;
		}

		return ownershipTicket;
	} catch (ex) {
		return null;
	}
};

/**
 * Parse a Steam app or session ticket and return an object containing its details. Static.
 * @param {Buffer|ByteBuffer} ticket - The binary appticket
 * @returns {object|null} - object if well-formed ticket (may not be valid), or null if not well-formed
 */
SteamUser.parseAppTicket = function(ticket) {
	// https://github.com/SteamRE/SteamKit/blob/master/Resources/Structs/steam3_appticket.hsl

	if (!ByteBuffer.isByteBuffer(ticket)) {
		ticket = ByteBuffer.wrap(ticket, ByteBuffer.LITTLE_ENDIAN);
	}

	let details = {};

	try {
		let initialLength = ticket.readUint32();
		if (initialLength == 20) {
			// This is a full appticket, with a GC token and session header (in addition to ownership ticket)
			details.authTicket = ticket.slice(ticket.offset - 4, ticket.offset - 4 + 52).toBuffer(); // this is the part that's passed back to Steam for validation

			details.gcToken = ticket.readUint64().toString();
			//details.steamID = new SteamID(ticket.readUint64().toString());
			ticket.skip(8); // the SteamID gets read later on
			details.tokenGenerated = new Date(ticket.readUint32() * 1000);

			if (ticket.readUint32() != 24) {
				// SESSIONHEADER should be 24 bytes.
				return null;
			}

			ticket.skip(8); // unknown 1 and unknown 2
			details.sessionExternalIP = Helpers.ipIntToString(ticket.readUint32());
			ticket.skip(4); // filler
			details.clientConnectionTime = ticket.readUint32(); // time the client has been connected to Steam in ms
			details.clientConnectionCount = ticket.readUint32(); // how many servers the client has connected to

			if (ticket.readUint32() + ticket.offset != ticket.limit) {
				// OWNERSHIPSECTIONWITHSIGNATURE sectlength
				return null;
			}
		} else {
			ticket.skip(-4);
		}

		// Start reading the ownership ticket
		let ownershipTicketOffset = ticket.offset;
		let ownershipTicketLength = ticket.readUint32(); // including itself, for some reason
		if (ownershipTicketOffset + ownershipTicketLength != ticket.limit && ownershipTicketOffset + ownershipTicketLength + 128 != ticket.limit) {
			return null;
		}

		let i, j, dlc;

		details.version = ticket.readUint32();
		details.steamID = new SteamID(ticket.readUint64().toString());
		details.appID = ticket.readUint32();
		details.ownershipTicketExternalIP = Helpers.ipIntToString(ticket.readUint32());
		details.ownershipTicketInternalIP = Helpers.ipIntToString(ticket.readUint32());
		details.ownershipFlags = ticket.readUint32();
		details.ownershipTicketGenerated = new Date(ticket.readUint32() * 1000);
		details.ownershipTicketExpires = new Date(ticket.readUint32() * 1000);
		details.licenses = [];

		let licenseCount = ticket.readUint16();
		for (i = 0; i < licenseCount; i++) {
			details.licenses.push(ticket.readUint32());
		}

		details.dlc = [];

		let dlcCount = ticket.readUint16();
		for (i = 0; i < dlcCount; i++) {
			dlc = {};
			dlc.appID = ticket.readUint32();
			dlc.licenses = [];

			licenseCount = ticket.readUint16();

			for (j = 0; j < licenseCount; j++) {
				dlc.licenses.push(ticket.readUint32());
			}

			details.dlc.push(dlc);
		}

		ticket.readUint16(); // reserved
		if (ticket.offset + 128 == ticket.limit) {
			// Has signature
			details.signature = ticket.slice(ticket.offset, ticket.offset + 128).toBuffer();
		}

		let date = new Date();
		details.isExpired = details.ownershipTicketExpires < date;
		details.hasValidSignature = !!details.signature && SteamCrypto.verifySignature(ticket.slice(ownershipTicketOffset, ownershipTicketOffset + ownershipTicketLength).toBuffer(), details.signature);
		details.isValid = !details.isExpired && (!details.signature || details.hasValidSignature);
	} catch (ex) {
		return null; // not a valid ticket
	}

	return details;
};

SteamUser.prototype.getAuthSessionTicket = function(appid, callback) {
	// For an auth session ticket we need the following:
	// 1. Length-prefixed GCTOKEN
	// 2. Length-prefixed SESSIONHEADER
	// 3. Length-prefixed OWNERSHIPTICKET (yes, even though the ticket itself has a length)
	// The GCTOKEN and SESSIONHEADER portion is passed to ClientAuthList for reuse validation
	this.getAppOwnershipTicket(appid, (err, ticket) => {
		if (err) {
			callback(err);
			return;
		}

		let buildToken = () => {
			let gcToken = this._gcTokens.splice(0, 1)[0];
			let buffer = new ByteBuffer(4 + gcToken.length + 4 + 24 + 4 + ticket.length, ByteBuffer.LITTLE_ENDIAN);
			buffer.writeUint32(gcToken.length);
			buffer.append(gcToken);
			buffer.writeUint32(24); // length of the session header, which is always 24 bytes
			buffer.writeUint32(1); // unknown 1
			buffer.writeUint32(2); // unknown 2
			buffer.writeUint32(Helpers.ipStringToInt(this.publicIP)); // external IP
			buffer.writeUint32(0); // filler
			buffer.writeUint32(Date.now() - this._connectTime); // timestamp
			buffer.writeUint32(++this._connectionCount); // connection count
			buffer.writeUint32(ticket.length);
			buffer.append(ticket);

			buffer = buffer.flip().toBuffer();

			// We need to activate our ticket
			this.validateAuthTickets(appid, buffer, function() {
				callback(null, buffer);
			});
		};

		// Do we have any GC tokens?
		if (this._gcTokens.length > 0) {
			buildToken();
		} else {
			this.once('_gcTokens', buildToken); // continue once we get some tokens
		}
	});
};

SteamUser.prototype.getAppOwnershipTicket = function(appid, callback) {
	let getNewTicket = () => {
		this._send(SteamUser.EMsg.ClientGetAppOwnershipTicket, {"app_id": appid}, (body) => {
			if (body.eresult != SteamUser.EResult.OK) {
				callback(Helpers.eresultError(body.eresult));
				return;
			}

			if (body.app_id != appid) {
				callback(new Error("Cannot get app ownership ticket"));
				return;
			}

			let ticket = body.ticket.toBuffer();
			if (ticket && ticket.length > 10 && this.options.saveAppTickets && this.storage) {
				this.storage.saveFile("appOwnershipTicket_" + this.steamID + "_" + appid + ".bin", ticket);
			}

			callback(null, body.ticket.toBuffer());
		});
	};

	// See if we have one saved
	if (this.storage) {
		this.storage.readFile("appOwnershipTicket_" + this.steamID + "_" + appid + ".bin", (err, file) => {
			if (!err && file) {
				let parsed = SteamUser.parseAppTicket(file);
				// Only return the saved ticket if it has a valid signature, expires more than 6 hours from now, and has the same external IP as we have right now.
				if (parsed && parsed.isValid && parsed.ownershipTicketExpires - Date.now() >= (1000 * 60 * 60 * 6) && parsed.ownershipTicketExternalIP == this.publicIP) {
					callback(null, file);
					return;
				}
			}

			getNewTicket();
		});
	} else {
		getNewTicket();
	}
};

// TODO: Does this list need to include all the tickets we consider active and valid, regardless of who we are?
// TODO make this internal and only expose an interface for adding/removing tickets
SteamUser.prototype.validateAuthTickets = function(appid, tickets, callback) {
	if (!(tickets instanceof Array)) {
		tickets = [tickets];
	}

	let obj = {
		"tokens_left": (this._gcTokens ? this._gcTokens.length : 0),
		"last_request_seq": this._authSeqMe,
		"last_request_seq_from_server": this._authSeqThem,
		"tickets": [],
		"app_ids": [],
		"message_sequence": ++this._authSeqMe
	};

	tickets.forEach((ticket, idx) => {
		let authTicket = null;

		if (ticket instanceof Buffer && ticket.length == 52) {
			authTicket = ticket;
		}

		if (ticket.authTicket) {
			authTicket = ticket.authTicket;
		} else {
			ticket = SteamUser.parseAppTicket(ticket);
			if (!ticket) {
				if (callback) {
					callback(new Error("Ticket " + idx + " is invalid"));
				}

				return;
			}

			authTicket = ticket.authTicket;
		}

		let sid = new SteamID();
		sid.universe = this.steamID.universe;
		sid.type = SteamID.Type.INDIVIDUAL;
		sid.instance = SteamID.Instance.DESKTOP;
		sid.accountid = authTicket.readUInt32LE(12);
		sid = sid.getSteamID64();

		let isOurTicket = (sid == this.steamID.getSteamID64());

		obj.tickets.push({
			"estate": isOurTicket ? 0 : 1,
			"steamid": isOurTicket ? 0 : sid,
			"gameid": appid,
			"h_steam_pipe": this._hSteamPipe,
			"ticket_crc": CRC32.unsigned(authTicket),
			"ticket": authTicket
		});

		obj.app_ids.push(appid);
	});

	if (tickets.length == 0) {
		obj.app_ids.push(appid); // canceling tickets for this app
	}

	this._send(SteamUser.EMsg.ClientAuthList, obj, (body) => {
		this._authSeqThem = body.message_sequence;

		if (callback) {
			callback(null);
		}
	});
};

SteamUser.prototype.cancelAuthTicket = function(appid, callback) {
	this.validateAuthTickets(appid, [], callback);
};

// Handlers

SteamUser.prototype._handlers[SteamUser.EMsg.ClientGameConnectTokens] = function(body) {
	this.emit('debug', "Received " + body.tokens.length + " game connect tokens");
	body.tokens.forEach((token) => {
		this._gcTokens.push(token.toBuffer());
	});

	this.emit('_gcTokens'); // internal private event
};

SteamUser.prototype._handlers[SteamUser.EMsg.ClientTicketAuthComplete] = function(body) {
	let eventBody = {
		"steamID": new SteamID(body.steam_id.toString()),
		"appOwnerSteamID": body.owner_steam_id.toString() == "0" ? null : new SteamID(body.owner_steam_id.toString()),
		"appID": parseInt(body.game_id.toString(), 10),
		"ticketCrc": body.ticket_crc,
		"state": body.estate,
		"authSessionResponse": body.eauth_session_response
	};

	let eventName = "authTicketValidation";
	if (eventBody.steamID.type == 0) {
		// this appears to happen when it's our own ticket?
		eventName = "authTicketStatus";
		eventBody.steamID = this.steamID;
	}

	this.emit(eventName, eventBody);
};
<commit_msg>Check salted sha1 hash<commit_after>const ByteBuffer = require('bytebuffer');
const CRC32 = require('buffer-crc32');
const Crypto = require('crypto');
const SteamCrypto = require('@doctormckay/steam-crypto');
const SteamID = require('steamid');

const Helpers = require('./helpers.js');
const Schema = require('./protobufs.js');
const SteamUser = require('../index.js');

/**
 * Request an encrypted appticket for a particular app. The app must be set up on the Steam backend for encrypted apptickets.
 * @param {int} appid - The Steam AppID of the app you want a ticket for
 * @param {Buffer} [userData] - If the app expects some "user data", provide it here
 * @param {function} callback - First argument is "err", second is the ticket as a Buffer (on success)
 */
SteamUser.prototype.getEncryptedAppTicket = function(appid, userData, callback) {
	if (typeof userData === 'function') {
		callback = userData;
		userData = new Buffer(0);
	}

	this._send(SteamUser.EMsg.ClientRequestEncryptedAppTicket, {"app_id": appid, "userdata": userData}, (body) => {
		if (body.eresult != SteamUser.EResult.OK) {
			callback(Helpers.eresultError(body.eresult));
			return;
		}

		if (body.app_id != appid) {
			// Don't know if this can even happen
			callback(new Error("Steam did not send an appticket for the requested appid"));
			return;
		}

		if (!body.encrypted_app_ticket) {
			callback(new Error("No appticket in response"));
			return;
		}

		callback(null, (new Schema.EncryptedAppTicket(body.encrypted_app_ticket)).toBuffer());
	});
};

/**
 *
 * @param {Buffer} ticket - The raw encrypted appticket
 * @param {Buffer|string} encryptionKey - The app's encryption key, either raw hex or a Buffer
 * @returns {object}
 */
SteamUser.parseEncryptedAppTicket = function(ticket, encryptionKey) {
	try {
		let outer = Schema.EncryptedAppTicket.decode(ticket);
		let key = typeof encryptionKey === 'string' ? Buffer.from(encryptionKey, 'hex') : encryptionKey;
		let decrypted = SteamCrypto.symmetricDecrypt(outer.encrypted_ticket.toBuffer(), key);

		if (CRC32.unsigned(decrypted) != outer.crc_encryptedticket) {
			return null;
		}

		// the beginning is the user-supplied data
		let userData = decrypted.slice(0, outer.cb_encrypteduserdata);
		let ownershipTicketLength = decrypted.readUInt32LE(outer.cb_encrypteduserdata);
		let ownershipTicket = SteamUser.parseAppTicket(decrypted.slice(outer.cb_encrypteduserdata, outer.cb_encrypteduserdata + ownershipTicketLength));
		if (ownershipTicket) {
			ownershipTicket.userData = userData;
		}

		let remainder = decrypted.slice(outer.cb_encrypteduserdata + ownershipTicketLength);
		if (remainder.length >= 8 + 20) {
			// salted sha1 hash
			let salt = remainder.slice(0, 8);
			let hash = remainder.slice(8, 28);
			remainder = remainder.slice(28);

			let sha1 = Crypto.createHash('sha1');
			sha1.update(decrypted.slice(0, outer.cb_encrypteduserdata + ownershipTicketLength))
				.update(salt);
			if (!hash.equals(sha1.digest())) {
				return null;
			}

			ownershipTicket.unknown1 = remainder.readUInt32LE(0);
		}

		return ownershipTicket;
	} catch (ex) {
		return null;
	}
};

/**
 * Parse a Steam app or session ticket and return an object containing its details. Static.
 * @param {Buffer|ByteBuffer} ticket - The binary appticket
 * @returns {object|null} - object if well-formed ticket (may not be valid), or null if not well-formed
 */
SteamUser.parseAppTicket = function(ticket) {
	// https://github.com/SteamRE/SteamKit/blob/master/Resources/Structs/steam3_appticket.hsl

	if (!ByteBuffer.isByteBuffer(ticket)) {
		ticket = ByteBuffer.wrap(ticket, ByteBuffer.LITTLE_ENDIAN);
	}

	let details = {};

	try {
		let initialLength = ticket.readUint32();
		if (initialLength == 20) {
			// This is a full appticket, with a GC token and session header (in addition to ownership ticket)
			details.authTicket = ticket.slice(ticket.offset - 4, ticket.offset - 4 + 52).toBuffer(); // this is the part that's passed back to Steam for validation

			details.gcToken = ticket.readUint64().toString();
			//details.steamID = new SteamID(ticket.readUint64().toString());
			ticket.skip(8); // the SteamID gets read later on
			details.tokenGenerated = new Date(ticket.readUint32() * 1000);

			if (ticket.readUint32() != 24) {
				// SESSIONHEADER should be 24 bytes.
				return null;
			}

			ticket.skip(8); // unknown 1 and unknown 2
			details.sessionExternalIP = Helpers.ipIntToString(ticket.readUint32());
			ticket.skip(4); // filler
			details.clientConnectionTime = ticket.readUint32(); // time the client has been connected to Steam in ms
			details.clientConnectionCount = ticket.readUint32(); // how many servers the client has connected to

			if (ticket.readUint32() + ticket.offset != ticket.limit) {
				// OWNERSHIPSECTIONWITHSIGNATURE sectlength
				return null;
			}
		} else {
			ticket.skip(-4);
		}

		// Start reading the ownership ticket
		let ownershipTicketOffset = ticket.offset;
		let ownershipTicketLength = ticket.readUint32(); // including itself, for some reason
		if (ownershipTicketOffset + ownershipTicketLength != ticket.limit && ownershipTicketOffset + ownershipTicketLength + 128 != ticket.limit) {
			return null;
		}

		let i, j, dlc;

		details.version = ticket.readUint32();
		details.steamID = new SteamID(ticket.readUint64().toString());
		details.appID = ticket.readUint32();
		details.ownershipTicketExternalIP = Helpers.ipIntToString(ticket.readUint32());
		details.ownershipTicketInternalIP = Helpers.ipIntToString(ticket.readUint32());
		details.ownershipFlags = ticket.readUint32();
		details.ownershipTicketGenerated = new Date(ticket.readUint32() * 1000);
		details.ownershipTicketExpires = new Date(ticket.readUint32() * 1000);
		details.licenses = [];

		let licenseCount = ticket.readUint16();
		for (i = 0; i < licenseCount; i++) {
			details.licenses.push(ticket.readUint32());
		}

		details.dlc = [];

		let dlcCount = ticket.readUint16();
		for (i = 0; i < dlcCount; i++) {
			dlc = {};
			dlc.appID = ticket.readUint32();
			dlc.licenses = [];

			licenseCount = ticket.readUint16();

			for (j = 0; j < licenseCount; j++) {
				dlc.licenses.push(ticket.readUint32());
			}

			details.dlc.push(dlc);
		}

		ticket.readUint16(); // reserved
		if (ticket.offset + 128 == ticket.limit) {
			// Has signature
			details.signature = ticket.slice(ticket.offset, ticket.offset + 128).toBuffer();
		}

		let date = new Date();
		details.isExpired = details.ownershipTicketExpires < date;
		details.hasValidSignature = !!details.signature && SteamCrypto.verifySignature(ticket.slice(ownershipTicketOffset, ownershipTicketOffset + ownershipTicketLength).toBuffer(), details.signature);
		details.isValid = !details.isExpired && (!details.signature || details.hasValidSignature);
	} catch (ex) {
		return null; // not a valid ticket
	}

	return details;
};

SteamUser.prototype.getAuthSessionTicket = function(appid, callback) {
	// For an auth session ticket we need the following:
	// 1. Length-prefixed GCTOKEN
	// 2. Length-prefixed SESSIONHEADER
	// 3. Length-prefixed OWNERSHIPTICKET (yes, even though the ticket itself has a length)
	// The GCTOKEN and SESSIONHEADER portion is passed to ClientAuthList for reuse validation
	this.getAppOwnershipTicket(appid, (err, ticket) => {
		if (err) {
			callback(err);
			return;
		}

		let buildToken = () => {
			let gcToken = this._gcTokens.splice(0, 1)[0];
			let buffer = new ByteBuffer(4 + gcToken.length + 4 + 24 + 4 + ticket.length, ByteBuffer.LITTLE_ENDIAN);
			buffer.writeUint32(gcToken.length);
			buffer.append(gcToken);
			buffer.writeUint32(24); // length of the session header, which is always 24 bytes
			buffer.writeUint32(1); // unknown 1
			buffer.writeUint32(2); // unknown 2
			buffer.writeUint32(Helpers.ipStringToInt(this.publicIP)); // external IP
			buffer.writeUint32(0); // filler
			buffer.writeUint32(Date.now() - this._connectTime); // timestamp
			buffer.writeUint32(++this._connectionCount); // connection count
			buffer.writeUint32(ticket.length);
			buffer.append(ticket);

			buffer = buffer.flip().toBuffer();

			// We need to activate our ticket
			this.validateAuthTickets(appid, buffer, function() {
				callback(null, buffer);
			});
		};

		// Do we have any GC tokens?
		if (this._gcTokens.length > 0) {
			buildToken();
		} else {
			this.once('_gcTokens', buildToken); // continue once we get some tokens
		}
	});
};

SteamUser.prototype.getAppOwnershipTicket = function(appid, callback) {
	let getNewTicket = () => {
		this._send(SteamUser.EMsg.ClientGetAppOwnershipTicket, {"app_id": appid}, (body) => {
			if (body.eresult != SteamUser.EResult.OK) {
				callback(Helpers.eresultError(body.eresult));
				return;
			}

			if (body.app_id != appid) {
				callback(new Error("Cannot get app ownership ticket"));
				return;
			}

			let ticket = body.ticket.toBuffer();
			if (ticket && ticket.length > 10 && this.options.saveAppTickets && this.storage) {
				this.storage.saveFile("appOwnershipTicket_" + this.steamID + "_" + appid + ".bin", ticket);
			}

			callback(null, body.ticket.toBuffer());
		});
	};

	// See if we have one saved
	if (this.storage) {
		this.storage.readFile("appOwnershipTicket_" + this.steamID + "_" + appid + ".bin", (err, file) => {
			if (!err && file) {
				let parsed = SteamUser.parseAppTicket(file);
				// Only return the saved ticket if it has a valid signature, expires more than 6 hours from now, and has the same external IP as we have right now.
				if (parsed && parsed.isValid && parsed.ownershipTicketExpires - Date.now() >= (1000 * 60 * 60 * 6) && parsed.ownershipTicketExternalIP == this.publicIP) {
					callback(null, file);
					return;
				}
			}

			getNewTicket();
		});
	} else {
		getNewTicket();
	}
};

// TODO: Does this list need to include all the tickets we consider active and valid, regardless of who we are?
// TODO make this internal and only expose an interface for adding/removing tickets
SteamUser.prototype.validateAuthTickets = function(appid, tickets, callback) {
	if (!(tickets instanceof Array)) {
		tickets = [tickets];
	}

	let obj = {
		"tokens_left": (this._gcTokens ? this._gcTokens.length : 0),
		"last_request_seq": this._authSeqMe,
		"last_request_seq_from_server": this._authSeqThem,
		"tickets": [],
		"app_ids": [],
		"message_sequence": ++this._authSeqMe
	};

	tickets.forEach((ticket, idx) => {
		let authTicket = null;

		if (ticket instanceof Buffer && ticket.length == 52) {
			authTicket = ticket;
		}

		if (ticket.authTicket) {
			authTicket = ticket.authTicket;
		} else {
			ticket = SteamUser.parseAppTicket(ticket);
			if (!ticket) {
				if (callback) {
					callback(new Error("Ticket " + idx + " is invalid"));
				}

				return;
			}

			authTicket = ticket.authTicket;
		}

		let sid = new SteamID();
		sid.universe = this.steamID.universe;
		sid.type = SteamID.Type.INDIVIDUAL;
		sid.instance = SteamID.Instance.DESKTOP;
		sid.accountid = authTicket.readUInt32LE(12);
		sid = sid.getSteamID64();

		let isOurTicket = (sid == this.steamID.getSteamID64());

		obj.tickets.push({
			"estate": isOurTicket ? 0 : 1,
			"steamid": isOurTicket ? 0 : sid,
			"gameid": appid,
			"h_steam_pipe": this._hSteamPipe,
			"ticket_crc": CRC32.unsigned(authTicket),
			"ticket": authTicket
		});

		obj.app_ids.push(appid);
	});

	if (tickets.length == 0) {
		obj.app_ids.push(appid); // canceling tickets for this app
	}

	this._send(SteamUser.EMsg.ClientAuthList, obj, (body) => {
		this._authSeqThem = body.message_sequence;

		if (callback) {
			callback(null);
		}
	});
};

SteamUser.prototype.cancelAuthTicket = function(appid, callback) {
	this.validateAuthTickets(appid, [], callback);
};

// Handlers

SteamUser.prototype._handlers[SteamUser.EMsg.ClientGameConnectTokens] = function(body) {
	this.emit('debug', "Received " + body.tokens.length + " game connect tokens");
	body.tokens.forEach((token) => {
		this._gcTokens.push(token.toBuffer());
	});

	this.emit('_gcTokens'); // internal private event
};

SteamUser.prototype._handlers[SteamUser.EMsg.ClientTicketAuthComplete] = function(body) {
	let eventBody = {
		"steamID": new SteamID(body.steam_id.toString()),
		"appOwnerSteamID": body.owner_steam_id.toString() == "0" ? null : new SteamID(body.owner_steam_id.toString()),
		"appID": parseInt(body.game_id.toString(), 10),
		"ticketCrc": body.ticket_crc,
		"state": body.estate,
		"authSessionResponse": body.eauth_session_response
	};

	let eventName = "authTicketValidation";
	if (eventBody.steamID.type == 0) {
		// this appears to happen when it's our own ticket?
		eventName = "authTicketStatus";
		eventBody.steamID = this.steamID;
	}

	this.emit(eventName, eventBody);
};
<|endoftext|>


<commit_before>import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { Link } from 'react-router';
import values from 'lodash/values';

import { questionProps } from '../../prop-types/question_props';

import Errors from '../Errors';
import ModalDialog from '../ModalDialog';
import ResponseSetModal from '../../containers/response_sets/ResponseSetModal';
import ResponseSetDragWidget from '../../containers/response_sets/ResponseSetDragWidget';
import CodedSetTableEditContainer from '../../containers/CodedSetTableEditContainer';

class QuestionEdit extends Component {

  constructor(props) {
    super(props);
    switch(this.props.action) {
      case 'revise':
        this.state = this.stateForRevise();
        break;
      case 'extend':
        this.state = this.stateForExtend();
        break;
      case 'new':
        this.state = this.stateForNew();
        break;
      default:
        this.state = this.stateForRevise();
    }
    this.handleResponseSetsChange = this.handleResponseSetsChange.bind(this);
    this.handleResponseSetSuccess = this.handleResponseSetSuccess.bind(this);
    this.unsavedState = false;
  }

  componentWillReceiveProps(nextProps) {
    if(this.state.responseTypeId === null || this.state.responseTypeId === undefined) {
      const sortedRT = this.sortedResponseTypes(nextProps.responseTypes);
      let rtid = sortedRT[0] ? sortedRT[0].id : null;
      this.setState({ responseTypeId: rtid });
    }
    if(nextProps.question && nextProps.question.responseSets && nextProps.question.responseSets !== this.state.linkedResponseSets) {
      this.setState({ linkedResponseSets: nextProps.question.responseSets });
    }
  }

  componentDidMount() {
    if(this.props.route && this.props.router){
      this.unbindHook = this.props.router.setRouteLeaveHook(this.props.route, this.routerWillLeave.bind(this));
    }
    this.existingOnBeforeUnload = window.onbeforeunload;
    window.onbeforeunload = this.windowWillUnload.bind(this);
  }

  componentWillUnmount() {
    this.unsavedState = false;
    window.onbeforeunload = this.existingOnBeforeUnload;
    if(this.unbindHook){
      this.unbindHook();
    }
  }

  routerWillLeave(nextLocation) {
    this.setState({ showWarningModal: this.unsavedState });
    this.nextLocation = nextLocation;
    return !this.unsavedState;
  }

  handleModalResponse(leavePage){
    this.setState({ showWarningModal: false });
    if(leavePage){
      this.unsavedState = false;
      this.props.router.push(this.nextLocation.pathname);
    }else{
      this.props.questionSubmitter(this.state, () => {
        this.unsavedState = false;
        this.props.router.push(this.nextLocation.pathname);
      }, (failureResponse) => {
        this.setState({errors: failureResponse.response.data});
      });
    }
  }

  windowWillUnload() {
    return (this.unsavedState || null);
  }

  modalInitialState() {
    return {
      showWarningModal: false,
      showResponseSetModal: false
    };
  }

  blankQuestionState() {
    return {
      content: '',
      description: '',
      questionTypeId: null,
      versionIndependentId: null,
      version: 1,
      responseTypeId: null,
      conceptsAttributes: [],
      linkedResponseSets: [],
      otherAllowed: false
    };
  }

  copyQuestion() {
    let questionType = this.props.question.category ? this.props.question.category : this.props.question.questionType;
    let questionCopy = {content: this.props.question.content,
      description: this.props.question.description,
      otherAllowed: this.props.question.otherAllowed,
      questionTypeId: questionType ? questionType.id : undefined,
      responseTypeId: this.props.question.responseType ? this.props.question.responseType.id : undefined};
    questionCopy.conceptsAttributes = filterConcepts(this.props.question.concepts);
    questionCopy.linkedResponseSets = this.props.question.responseSets || [];
    return questionCopy;
  }

  stateForRevise() {
    let reviseState = this.modalInitialState();
    Object.assign(reviseState, this.copyQuestion());
    if (this.props.action === 'revise') {
      reviseState.version = this.props.question.version + 1;
    }
    reviseState.versionIndependentId = this.props.question.versionIndependentId;
    reviseState.parentId  = this.props.question.parent ? this.props.question.parent.id : ''; // null is not allowed as a value
    return reviseState;
  }

  stateForNew() {
    let state = this.modalInitialState();
    Object.assign(state, this.blankQuestionState());
    return state;
  }

  //not working because of map => questionType
<<<<<< extendState[k] = question[k] || v);
    extendState.conceptsAttributes = filterConcepts(question.concepts);
    extendState.linkedResponseSets = question.responseSets || [];
>>>>>>> Refactored questionEdit with full RS objects
    extendState.version = 1;
    extendState.parentId  = this.props.question.id;
    extendState.oid = '';
    extendState.versionIndependentId = null;
    return extendState;
  }

  render(){
    const {question, questionTypes, responseTypes} = this.props;
    const state = this.state;
    if(!question || !questionTypes || !responseTypes){
      return (
Loading....
);
    }
    return (
      <form id="question-edit-form" onSubmit={(e) => this.handleSubmit(e)}>
      <ModalDialog  show ={this.state.showWarningModal}
                    title="Warning"
                    subTitle="Unsaved Changes"
                    warning={true}
                    message="You are about to leave a page with unsaved changes. How would you like to proceed?"
                    secondaryButtonMessage="Continue Without Saving"
                    primaryButtonMessage="Save & Leave"
                    cancelButtonMessage="Cancel"
                    primaryButtonAction={()=> this.handleModalResponse(false)}
                    cancelButtonAction ={()=> {
                      this.props.router.push(this.props.route.path);
                      this.setState({ showWarningModal: false });
                    }}
                    secondaryButtonAction={()=> this.handleModalResponse(true)} />
        <ResponseSetModal show={this.state.showResponseSetModal || false}
                          closeModal={() => this.setState({showResponseSetModal: false})}
                          saveResponseSetSuccess={this.handleResponseSetSuccess} />
        <Errors errors={this.state.errors} />

{`${this.actionWord()} Question`}

                      Question
                      <input className="input-format" placeholder="Question text" type="text" name="content" id="content" defaultValue={state.content} onChange={this.handleChange('content')} />

                      Category
                      <select className="input-select" name="questionTypeId" id="questionTypeId" value={state.questionTypeId || undefined} onChange={this.handleChange('questionTypeId')} >
                        <option value=""></option>
                        {questionTypes && values(questionTypes).map((qt) => {
                          return <option key={qt.id} value={qt.id}>{qt.name}</option>;
                        })}
                      </select>

                    Description
                    <textarea className="input-format" placeholder="Question description" type="text" name="question-description" id="question-description" defaultValue={state.description} onChange={this.handleChange('description')} />

                  Response Type
                    <select name="responseTypeId" id="responseTypeId" className="input-select" value={state.responseTypeId || undefined} onChange={this.handleResponseTypeChange()} >
                      {this.sortedResponseTypes(this.props.responseTypes).map((rt) => {
                        return (<option key={rt.id} value={rt.id} >{rt.name} - {rt.description}</option>);
                      })}
                    </select>

                {this.otherAllowedBox()}

Tags

                    <CodedSetTableEditContainer itemWatcher={(r) => this.handleConceptsChange(r)}
                             initialItems={this.state.conceptsAttributes}
                             parentName={'question'}
                             childName={'tag'} />

                { this.isChoiceType() ?

Response Sets

Selected Response Sets

                    <button className="btn btn-primary add-new-response-set" type="button" id="add-new-response-set" onClick={() => this.setState({showResponseSetModal:true})}>Add New Response Set</button>

                : ''}
                { this.isChoiceType() ?
                  <ResponseSetDragWidget handleResponseSetsChange={this.handleResponseSetsChange}
                                         selectedResponseSets={this.state.linkedResponseSets} />
                : ''}

                    <button type="submit" name="commit" id='submit-question-form' className="btn btn-default" data-disable-with="Save">Save</button>
                    {this.cancelButton()}

      </form>
    );
  }

  actionWord() {
    const wordMap = {'new': 'Create', 'revise': 'Revise', 'extend': 'Extend', 'edit': 'Edit Draft of'};
    return wordMap[this.props.action];
  }

  isChoiceType(){
    let rt = values(this.props.responseTypes).find((a) => {
      return a.id == this.state.responseTypeId;
    });
    if(rt && (rt.code == "choice" || rt.code == "open-choice")){
      return true;
    }
  }

  sortedResponseTypes(responseTypes){
    return values(responseTypes).sort((a,b) => {
      if(a.name == b.name ){
        return 0;
      }else if(a.name < b.name){
        return -1;
      }
      return 1;
    });
  }
  cancelButton() {
    if (this.props.question && this.props.question.id) {
      return(<Link className="btn btn-default" to={`/questions/${this.props.question.id}`}>Cancel</Link>);
    }
    return(<Link className="btn btn-default" to='/'>Cancel</Link>);
  }

  handleResponseSetSuccess(successResponse){
    this.handleResponseSetsChange(this.state.linkedResponseSets.concat([successResponse.data]));
    this.setState({showResponseSetModal: false});
  }

  handleSubmit(event) {
    event.preventDefault();
    if (this.props.action === 'edit') {
      this.props.draftSubmitter(this.props.id, this.state, (response) => {
        // TODO: Handle when the saving question fails.
        this.unsavedState = false;
        if (response.status === 200) {
          this.props.router.push(`/questions/${response.data.id}`);
        }
      });
    } else {
      this.props.questionSubmitter(this.state, (successResponse) => {
        this.unsavedState = false;
        this.props.router.push(`/questions/${successResponse.data.id}`);
      }, (failureResponse) => {
        this.setState({errors: failureResponse.response.data});
      });
    }
  }

  handleConceptsChange(newConcepts) {
    this.setState({conceptsAttributes: filterConcepts(newConcepts)});
    this.unsavedState = true;
  }

  handleResponseTypeChange() {
    return (event) => {
      this.setState({responseTypeId: event.target.value});
      if (this.props.responseTypes[event.target.value].code !== 'choice') {
        this.setState({otherAllowed: false});
      }
      this.unsavedState = true;
      if(this.props.handleResponseTypeChange){
        this.props.handleResponseTypeChange(this.props.responseTypes[event.target.value]);
      }
    };
  }

  handleChange(field) {
    return (event) => {
      let newState = {};
      newState[field] = event.target.value;
      this.setState(newState);
      this.unsavedState = true;
    };
  }

  toggleOtherAllowed() {
    this.setState({otherAllowed: !this.state.otherAllowed});
  }

  otherAllowedBox() {
    if (this.state.responseTypeId && this.props.responseTypes[this.state.responseTypeId].code === 'choice') {
      return (

            Other Allowed:
            <input className="form-ckeck-input" type="checkbox" name="otherAllowed" id="otherAllowed" checked={this.state.otherAllowed} onChange={() => this.toggleOtherAllowed()} />

      );
    } else {
      return '';
    }
  }

  handleResponseSetsChange(newResponseSets){
    this.setState({linkedResponseSets: newResponseSets});
    this.unsavedState = true;
  }
}

function filterConcepts(concepts) {
  if(!concepts){
    return [];
  }
  return concepts.filter((nc) => {
    return (nc.value !=='' ||  nc.codeSystem !== '' || nc.displayName !=='');
  }).map((nc) => {
    return {value: nc.value, codeSystem: nc.codeSystem, displayName: nc.displayName};
  });
}

QuestionEdit.propTypes = {
  id: PropTypes.string,
  route:  PropTypes.object,
  router: PropTypes.object,
  action: PropTypes.string,
  question: questionProps,
  questionTypes: PropTypes.object,
  responseTypes: PropTypes.object,
  draftSubmitter: PropTypes.func.isRequired,
  questionSubmitter: PropTypes.func.isRequired,
  handleResponseTypeChange: PropTypes.func
};

export default QuestionEdit;
<commit_msg>Fixing bug with disappearing linkedResponseSets<commit_after>import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { Link } from 'react-router';
import values from 'lodash/values';

import { questionProps } from '../../prop-types/question_props';

import Errors from '../Errors';
import ModalDialog from '../ModalDialog';
import ResponseSetModal from '../../containers/response_sets/ResponseSetModal';
import ResponseSetDragWidget from '../../containers/response_sets/ResponseSetDragWidget';
import CodedSetTableEditContainer from '../../containers/CodedSetTableEditContainer';

class QuestionEdit extends Component {

  constructor(props) {
    super(props);
    switch(this.props.action) {
      case 'revise':
        this.state = this.stateForRevise();
        break;
      case 'extend':
        this.state = this.stateForExtend();
        break;
      case 'new':
        this.state = this.stateForNew();
        break;
      default:
        this.state = this.stateForRevise();
    }
    this.handleResponseSetsChange = this.handleResponseSetsChange.bind(this);
    this.handleResponseSetSuccess = this.handleResponseSetSuccess.bind(this);
    this.unsavedState = false;
  }

  componentWillReceiveProps(nextProps) {
    if(this.state.responseTypeId === null || this.state.responseTypeId === undefined) {
      const sortedRT = this.sortedResponseTypes(nextProps.responseTypes);
      let rtid = sortedRT[0] ? sortedRT[0].id : null;
      this.setState({ responseTypeId: rtid });
    }
    if(nextProps.question && nextProps.question.responseSets && !this.unsavedState && nextProps.question.responseSets !== this.state.linkedResponseSets) {
      this.setState({ linkedResponseSets: nextProps.question.responseSets });
      console.log(nextProps.question.responseSets);
    }
  }

  componentDidMount() {
    if(this.props.route && this.props.router){
      this.unbindHook = this.props.router.setRouteLeaveHook(this.props.route, this.routerWillLeave.bind(this));
    }
    this.existingOnBeforeUnload = window.onbeforeunload;
    window.onbeforeunload = this.windowWillUnload.bind(this);
  }

  componentWillUnmount() {
    this.unsavedState = false;
    window.onbeforeunload = this.existingOnBeforeUnload;
    if(this.unbindHook){
      this.unbindHook();
    }
  }

  routerWillLeave(nextLocation) {
    this.setState({ showWarningModal: this.unsavedState });
    this.nextLocation = nextLocation;
    return !this.unsavedState;
  }

  handleModalResponse(leavePage){
    this.setState({ showWarningModal: false });
    if(leavePage){
      this.unsavedState = false;
      this.props.router.push(this.nextLocation.pathname);
    }else{
      this.props.questionSubmitter(this.state, () => {
        this.unsavedState = false;
        this.props.router.push(this.nextLocation.pathname);
      }, (failureResponse) => {
        this.setState({errors: failureResponse.response.data});
      });
    }
  }

  windowWillUnload() {
    return (this.unsavedState || null);
  }

  modalInitialState() {
    return {
      showWarningModal: false,
      showResponseSetModal: false
    };
  }

  blankQuestionState() {
    return {
      content: '',
      description: '',
      questionTypeId: null,
      versionIndependentId: null,
      version: 1,
      responseTypeId: null,
      conceptsAttributes: [],
      linkedResponseSets: [],
      otherAllowed: false
    };
  }

  copyQuestion() {
    let questionType = this.props.question.category ? this.props.question.category : this.props.question.questionType;
    let questionCopy = {content: this.props.question.content,
      description: this.props.question.description,
      otherAllowed: this.props.question.otherAllowed,
      questionTypeId: questionType ? questionType.id : undefined,
      responseTypeId: this.props.question.responseType ? this.props.question.responseType.id : undefined};
    questionCopy.conceptsAttributes = filterConcepts(this.props.question.concepts);
    questionCopy.linkedResponseSets = this.props.question.responseSets || [];
    return questionCopy;
  }

  stateForRevise() {
    let reviseState = this.modalInitialState();
    Object.assign(reviseState, this.copyQuestion());
    if (this.props.action === 'revise') {
      reviseState.version = this.props.question.version + 1;
    }
    reviseState.versionIndependentId = this.props.question.versionIndependentId;
    reviseState.parentId  = this.props.question.parent ? this.props.question.parent.id : ''; // null is not allowed as a value
    return reviseState;
  }

  stateForNew() {
    let state = this.modalInitialState();
    Object.assign(state, this.blankQuestionState());
    return state;
  }

  //not working because of map => questionType
<<<<<< extendState[k] = question[k] || v);
    extendState.conceptsAttributes = filterConcepts(question.concepts);
    extendState.linkedResponseSets = question.responseSets || [];
>>>>>>> Refactored questionEdit with full RS objects
    extendState.version = 1;
    extendState.parentId  = this.props.question.id;
    extendState.oid = '';
    extendState.versionIndependentId = null;
    return extendState;
  }

  render(){
    const {question, questionTypes, responseTypes} = this.props;
    const state = this.state;
    if(!question || !questionTypes || !responseTypes){
      return (
Loading....
);
    }
    return (
      <form id="question-edit-form" onSubmit={(e) => this.handleSubmit(e)}>
      <ModalDialog  show ={this.state.showWarningModal}
                    title="Warning"
                    subTitle="Unsaved Changes"
                    warning={true}
                    message="You are about to leave a page with unsaved changes. How would you like to proceed?"
                    secondaryButtonMessage="Continue Without Saving"
                    primaryButtonMessage="Save & Leave"
                    cancelButtonMessage="Cancel"
                    primaryButtonAction={()=> this.handleModalResponse(false)}
                    cancelButtonAction ={()=> {
                      this.props.router.push(this.props.route.path);
                      this.setState({ showWarningModal: false });
                    }}
                    secondaryButtonAction={()=> this.handleModalResponse(true)} />
        <ResponseSetModal show={this.state.showResponseSetModal || false}
                          closeModal={() => this.setState({showResponseSetModal: false})}
                          saveResponseSetSuccess={this.handleResponseSetSuccess} />
        <Errors errors={this.state.errors} />

{`${this.actionWord()} Question`}

                      Question
                      <input className="input-format" placeholder="Question text" type="text" name="content" id="content" defaultValue={state.content} onChange={this.handleChange('content')} />

                      Category
                      <select className="input-select" name="questionTypeId" id="questionTypeId" value={state.questionTypeId || undefined} onChange={this.handleChange('questionTypeId')} >
                        <option value=""></option>
                        {questionTypes && values(questionTypes).map((qt) => {
                          return <option key={qt.id} value={qt.id}>{qt.name}</option>;
                        })}
                      </select>

                    Description
                    <textarea className="input-format" placeholder="Question description" type="text" name="question-description" id="question-description" defaultValue={state.description} onChange={this.handleChange('description')} />

                  Response Type
                    <select name="responseTypeId" id="responseTypeId" className="input-select" value={state.responseTypeId || undefined} onChange={this.handleResponseTypeChange()} >
                      {this.sortedResponseTypes(this.props.responseTypes).map((rt) => {
                        return (<option key={rt.id} value={rt.id} >{rt.name} - {rt.description}</option>);
                      })}
                    </select>

                {this.otherAllowedBox()}

Tags

                    <CodedSetTableEditContainer itemWatcher={(r) => this.handleConceptsChange(r)}
                             initialItems={this.state.conceptsAttributes}
                             parentName={'question'}
                             childName={'tag'} />

                { this.isChoiceType() ?

Response Sets

Selected Response Sets

                    <button className="btn btn-primary add-new-response-set" type="button" id="add-new-response-set" onClick={() => this.setState({showResponseSetModal:true})}>Add New Response Set</button>

                : ''}
                { this.isChoiceType() ?
                  <ResponseSetDragWidget handleResponseSetsChange={this.handleResponseSetsChange}
                                         selectedResponseSets={this.state.linkedResponseSets} />
                : ''}

                    <button type="submit" name="commit" id='submit-question-form' className="btn btn-default" data-disable-with="Save">Save</button>
                    {this.cancelButton()}

      </form>
    );
  }

  actionWord() {
    const wordMap = {'new': 'Create', 'revise': 'Revise', 'extend': 'Extend', 'edit': 'Edit Draft of'};
    return wordMap[this.props.action];
  }

  isChoiceType(){
    let rt = values(this.props.responseTypes).find((a) => {
      return a.id == this.state.responseTypeId;
    });
    if(rt && (rt.code == "choice" || rt.code == "open-choice")){
      return true;
    }
  }

  sortedResponseTypes(responseTypes){
    return values(responseTypes).sort((a,b) => {
      if(a.name == b.name ){
        return 0;
      }else if(a.name < b.name){
        return -1;
      }
      return 1;
    });
  }
  cancelButton() {
    if (this.props.question && this.props.question.id) {
      return(<Link className="btn btn-default" to={`/questions/${this.props.question.id}`}>Cancel</Link>);
    }
    return(<Link className="btn btn-default" to='/'>Cancel</Link>);
  }

  handleResponseSetSuccess(successResponse){
    this.handleResponseSetsChange(this.state.linkedResponseSets.concat([successResponse.data]));
    this.setState({showResponseSetModal: false});
  }

  handleSubmit(event) {
    event.preventDefault();
    if (this.props.action === 'edit') {
      this.props.draftSubmitter(this.props.id, this.state, (response) => {
        // TODO: Handle when the saving question fails.
        this.unsavedState = false;
        if (response.status === 200) {
          this.props.router.push(`/questions/${response.data.id}`);
        }
      });
    } else {
      this.props.questionSubmitter(this.state, (successResponse) => {
        this.unsavedState = false;
        this.props.router.push(`/questions/${successResponse.data.id}`);
      }, (failureResponse) => {
        this.setState({errors: failureResponse.response.data});
      });
    }
  }

  handleConceptsChange(newConcepts) {
    this.setState({conceptsAttributes: filterConcepts(newConcepts)});
    this.unsavedState = true;
  }

  handleResponseTypeChange() {
    return (event) => {
      this.setState({responseTypeId: event.target.value});
      if (this.props.responseTypes[event.target.value].code !== 'choice') {
        this.setState({otherAllowed: false});
      }
      this.unsavedState = true;
      if(this.props.handleResponseTypeChange){
        this.props.handleResponseTypeChange(this.props.responseTypes[event.target.value]);
      }
    };
  }

  handleChange(field) {
    return (event) => {
      let newState = {};
      newState[field] = event.target.value;
      this.setState(newState);
      this.unsavedState = true;
    };
  }

  toggleOtherAllowed() {
    this.setState({otherAllowed: !this.state.otherAllowed});
  }

  otherAllowedBox() {
    if (this.state.responseTypeId && this.props.responseTypes[this.state.responseTypeId].code === 'choice') {
      return (

            Other Allowed:
            <input className="form-ckeck-input" type="checkbox" name="otherAllowed" id="otherAllowed" checked={this.state.otherAllowed} onChange={() => this.toggleOtherAllowed()} />

      );
    } else {
      return '';
    }
  }

  handleResponseSetsChange(newResponseSets){
    this.setState({linkedResponseSets: newResponseSets});
    this.unsavedState = true;
  }
}

function filterConcepts(concepts) {
  if(!concepts){
    return [];
  }
  return concepts.filter((nc) => {
    return (nc.value !=='' ||  nc.codeSystem !== '' || nc.displayName !=='');
  }).map((nc) => {
    return {value: nc.value, codeSystem: nc.codeSystem, displayName: nc.displayName};
  });
}

QuestionEdit.propTypes = {
  id: PropTypes.string,
  route:  PropTypes.object,
  router: PropTypes.object,
  action: PropTypes.string,
  question: questionProps,
  questionTypes: PropTypes.object,
  responseTypes: PropTypes.object,
  draftSubmitter: PropTypes.func.isRequired,
  questionSubmitter: PropTypes.func.isRequired,
  handleResponseTypeChange: PropTypes.func
};

export default QuestionEdit;
<|endoftext|>


<commit_before>// require deps
var fs = require('fs-extra');
var fingerprint = require('./fingerprint');

// save caches
var caches = {};

/**
 * Create a new module instance cache
 *
 * @public
 * @param {string} The module instance cache name.
 * @param {function} The factory method.
 */
module.exports = function (name, factory) {

    // return existing cache
    if (caches[name]) {
        return caches[name];
    }

    // clone cache class
    var cache = engine.clone(Cache);

    // save factory method
    cache.factory = factory;

    // cache module packages info
    cache._modules = {};

    // cache the running module instances
    cache._data = {};

    // save cache
    caches[name] = cache;

    return cache;
};

/**
 * The composition cache class.
 *
 * @class Cache
 */
var Cache = {

    /**
     * Remove a module instance or empty cache.
     *
     * @public
     * @param {string} The module instance name.
     * @param {string} The role name.
     */
    rm: function (name, role) {

        // remove one item
        if (name) {

            if (!this._data[name]) {
                return;
            }

            // check role access
            if (!engine.roleAccess(this._data[name], role)) {
                return 0;
            }

            // remove cached item
            delete this._data[name];

            return;
        }

        // remove all items
        this._data = {};
    },

    /**
     * Get or create a module instance.
     *
     * @public
     * @param {string} The module instance name.
     * @param {string} The role name.
     * @param {function} The callback function.
     */
    get: function (name, role, callback) {
        var self = this;

        // check if object is already in cache
        if (self._data[name]) {

            var data = self._data[name];
            var error;

            // check role access
            if (!engine.roleAccess(self._data[name], role)) {
                data = undefined;
                error = new Error('access denied.');
            }

            // return cached instance or call the callback
            return callback ? callback(error, data) : error || data;
        }

        // dont load composition, when no callback is given
        if (!callback) {
            return;
        }

        // create path to composition file
        var path =  engine.paths.app_composition + name + '.json';

        // read the composition json
        fs.readJson(path, function (err, composition) {

            // handle error and check role access
            if (err || !engine.roleAccess(composition, role)) {
                return callback(err || new Error('access denied.'));
            }

            // watch file
            if (self._data[name] !== null) {
                fs.watch(path, function (event) {

                    // remove cached data on file change, to force reload
                    if (event === 'change' && self._data[name]) {

                        // remove the cached item
                        self._data[name] = null;
                    }
                });
            }

            // get the module's package infos
            getPackackeInfo.call(self, composition.module, function (err, moduleConfig) {

                if (err) {
                    return callback(err);
                }

                // extend module instance config with the module dependencies
                composition.client = composition.client || {};

                if (moduleConfig) {

                    // all dependencies of the instance dependencies must be in this array!
                    // while installing a module, save all dependencies in the module deps
                    // dependencies: ['./file.js', 's/o/modA/v']
                    // => ModA deps: ['./file.js', 's/o/modB/v'] => ModB deps: []
                    // => to client: ['./file.js', 's/o/n/v/file.js']
                    if (moduleConfig.clientDependencies) {
                        composition.client.scripts = moduleConfig.clientDependencies;
                    }

                    // save main start script
                    if (moduleConfig.main) {
                        composition.main = moduleConfig.main;
                    }
                }

                // create a new module instance
                self.factory(composition, function (err, module_instance) {

                    // handle error
                    if (err) {
                        return callback(err);
                    }

                    // save module instance in cache
                    self._data[name] = module_instance;

                    // save the module instance always under the configured name
                    if (composition.name !== name) {
                        self._data[composition.name] = module_instance;
                    }

                    // return data
                    callback(null, module_instance);
                });
            });
        });
    }
};

/**
 * Parse and get the engine package infos.
 *
 * @private
 * @param {string} The module name or the custom module config object.
 * @param {function} The callback function.
 */
function getPackackeInfo (module, callback) {
    var self = this;

    // handle custom modules
    if (typeof module === 'object') {

        // return custom module package
        if (!module.clientDependencies) {
            return callback(null, module);
        }

        // add fingerprints to custom module dependencies
        fingerprint.addToFiles(module, module.clientDependencies, function (err, scripts) {

            if (err) {
                return callback(err);
            }

            // update the module dependencies with new script paths
            module.clientDependencies = scripts;

            // return the custom module package
            callback(null, module);
        });

        return;
    }

    // return cached object
    if (self._modules[module]) {
        return callback(null, self._modules[module]);
    }

    // create module path
    var path = engine.paths.app_modules + module + '/package.json';

    // read and parse the packackage json file
    fs.readJson(path, function (err, packageInfo) {

        // handle errors
        if (err || !packageInfo) {
            return callback(err || new Error('No module package found.'));
        }

        // update file on change
        // TODO remove all module instance, when module changes
        if (self._data[module] !== null) {
            fs.watch(path, function (event) {

                // remove cached data on file change, to force reload
                if (event === 'change' && self._modules[module]) {

                    // remove the cached item
                    self._modules[module] = null;
                }
            });
        }

        // save package info in cache
        self._modules[module] = packageInfo;

        // return the engine spezific package info
        if (!packageInfo.clientDependencies) {
            return callback(null, self._modules[module]);
        }

        // add fingerprints to client side dependencies
        fingerprint.addToFiles(module, packageInfo.clientDependencies, function (err, scripts) {

            if (err) {
                return callback(err);
            }

            // update the module dependencies with new script paths
            self._modules[module].clientDependencies = scripts;

            // return the module package info
            callback(null, self._modules[module]);
        });
    });
}
<commit_msg>added TODO comment<commit_after>// require deps
var fs = require('fs-extra');
var fingerprint = require('./fingerprint');

// save caches
var caches = {};

/**
 * Create a new module instance cache
 *
 * @public
 * @param {string} The module instance cache name.
 * @param {function} The factory method.
 */
module.exports = function (name, factory) {

    // return existing cache
    if (caches[name]) {
        return caches[name];
    }

    // clone cache class
    var cache = engine.clone(Cache);

    // save factory method
    cache.factory = factory;

    // cache module packages info
    cache._modules = {};

    // cache the running module instances
    cache._data = {};

    // save cache
    caches[name] = cache;

    return cache;
};

/**
 * The composition cache class.
 *
 * @class Cache
 */
var Cache = {

    /**
     * Remove a module instance or empty cache.
     *
     * @public
     * @param {string} The module instance name.
     * @param {string} The role name.
     */
    rm: function (name, role) {

        // remove one item
        if (name) {

            if (!this._data[name]) {
                return;
            }

            // check role access
            if (!engine.roleAccess(this._data[name], role)) {
                return 0;
            }

            // remove cached item
            delete this._data[name];

            return;
        }

        // remove all items
        this._data = {};
    },

    /**
     * Get or create a module instance.
     *
     * @public
     * @param {string} The module instance name.
     * @param {string} The role name.
     * @param {function} The callback function.
     */
    get: function (name, role, callback) {
        var self = this;

        // check if object is already in cache
        if (self._data[name]) {

            var data = self._data[name];
            var error;

            // check role access
            if (!engine.roleAccess(self._data[name], role)) {
                data = undefined;
                error = new Error('access denied.');
            }

            // return cached instance or call the callback
            return callback ? callback(error, data) : error || data;
        }

        // dont load composition, when no callback is given
        if (!callback) {
            return;
        }

        // create path to composition file
        var path =  engine.paths.app_composition + name + '.json';

        // read the composition json
        fs.readJson(path, function (err, composition) {

            // handle error and check role access
            if (err || !engine.roleAccess(composition, role)) {
                return callback(err || new Error('access denied.'));
            }

            // watch file
            if (self._data[name] !== null) {
                fs.watch(path, function (event) {

                    // remove cached data on file change, to force reload
                    if (event === 'change' && self._data[name]) {

                        // remove the cached item
                        self._data[name] = null;
                    }
                });
            }

            // get the module's package infos
            getPackackeInfo.call(self, composition.module, function (err, moduleConfig) {

                if (err) {
                    return callback(err);
                }

                // extend module instance config with the module dependencies
                composition.client = composition.client || {};

                if (moduleConfig) {

                    // all dependencies of the instance dependencies must be in this array!
                    // while installing a module, save all dependencies in the module deps
                    // dependencies: ['./file.js', 's/o/modA/v']
                    // => ModA deps: ['./file.js', 's/o/modB/v'] => ModB deps: []
                    // => to client: ['./file.js', 's/o/n/v/file.js']
                    if (moduleConfig.clientDependencies) {
                        composition.client.scripts = moduleConfig.clientDependencies;
                    }

                    // save main start script
                    if (moduleConfig.main) {
                        composition.main = moduleConfig.main;
                    }
                }

                // create a new module instance
                self.factory(composition, function (err, module_instance) {

                    // handle error
                    if (err) {
                        return callback(err);
                    }

                    // save module instance in cache
                    self._data[name] = module_instance;

                    // save the module instance always under the configured name
                    if (composition.name !== name) {
                        self._data[composition.name] = module_instance;
                    }

                    // return data
                    callback(null, module_instance);
                });
            });
        });
    }
};

/**
 * Parse and get the engine package infos.
 *
 * @private
 * @param {string} The module name or the custom module config object.
 * @param {function} The callback function.
 */
function getPackackeInfo (module, callback) {
    var self = this;

    // handle custom modules
    if (typeof module === 'object') {

        // return custom module package
        if (!module.clientDependencies) {
            return callback(null, module);
        }

        // add fingerprints to custom module dependencies
        // TODO check if finterprints are already added
        fingerprint.addToFiles(module, module.clientDependencies, function (err, scripts) {

            if (err) {
                return callback(err);
            }

            // update the module dependencies with new script paths
            module.clientDependencies = scripts;

            // return the custom module package
            callback(null, module);
        });

        return;
    }

    // return cached object
    if (self._modules[module]) {
        return callback(null, self._modules[module]);
    }

    // create module path
    var path = engine.paths.app_modules + module + '/package.json';

    // read and parse the packackage json file
    fs.readJson(path, function (err, packageInfo) {

        // handle errors
        if (err || !packageInfo) {
            return callback(err || new Error('No module package found.'));
        }

        // update file on change
        // TODO remove all module instance, when module changes
        if (self._data[module] !== null) {
            fs.watch(path, function (event) {

                // remove cached data on file change, to force reload
                if (event === 'change' && self._modules[module]) {

                    // remove the cached item
                    self._modules[module] = null;
                }
            });
        }

        // save package info in cache
        self._modules[module] = packageInfo;

        // return the engine spezific package info
        if (!packageInfo.clientDependencies) {
            return callback(null, self._modules[module]);
        }

        // add fingerprints to client side dependencies
        fingerprint.addToFiles(module, packageInfo.clientDependencies, function (err, scripts) {

            if (err) {
                return callback(err);
            }

            // update the module dependencies with new script paths
            self._modules[module].clientDependencies = scripts;

            // return the module package info
            callback(null, self._modules[module]);
        });
    });
}
<|endoftext|>


<commit_before>var sys = require('sys');
var events = require('events');
var path = require('path');

var copy = function copy(src, dst, callback) {
  var self = this;

  if(!callback) {
    callback = function(){};
  }

  self.on('error', function(err) {
    callback(err);
  });

  self.on('validations', function() {

    path.exists(src, function(exists) {

      if(!exists) {

        self.emit('error', new Error(src + ' does not exists. Nothing to be copied'));
        return;
      }

      fs.stat(src, function(err, stat) {

        if(err) {

          self.emit('error', err);
          return;
        }

        if(stat.isDirectory()) {

          self.emit('error', new Error(src + ' is a directory. It must be a file'));
          return;
        }

        if(src == dst) {

          self.emit('error', new Error(src + ' and ' + dst + 'are identical'));
          return;
        }

        self.emit('open_infd');
      });
    });
  });

  self.on('open_infd', function() {

    fs.open(src, 'r', function(err, infd) {

      if(err) {

        self.emit('error', err);
        return;
      }

      self.emit('open_outfd', infd);
    });

  });

  self.on('open_outfd', function(infd) {

    fs.open(dst, 'w', function(err, outfd) {

      if(err) {

        self.emit('error', err);
        return;
      }

      self.emit('sendfile', infd, outfd);
    });
  });

  self.on('sendfile', function(infd, outfd) {

    fs.fstat(infd, function(err, stat) {

      if(err) {

        self.emit('error', err);
        return;
      }

      fs.sendfile(outfd, infd, 0, stat.size, function() {

        self.emit('close_fds', infd, outfd);
        callback();
      });
    });
  });

  self.on('close_fds', function(infd, outfd) {

    fs.close(infd, function(err) {

      if(err) {
        self.emit('error', err);
      }

    });

    fs.close(outfd, function(err) {

      if(err) {

        self.emit('error', err);
      }

    });
  });

  self.emit('validations');
};
sys.inherits(copy, events.EventEmitter);

exports.copy = function(src, dst, callback) {

  return new copy(src, dst, callback);
};

exports.copySync = function copySync(src, dst) {

  if(typeof dst == 'undefined') {

		throw new Error('Destination is not defined');
  }

  if(!path.existsSync(src)) {

    throw new Error(src + ' does not exists.');
  }

  if(fs.statSync(src).isDirectory()) {

    throw new Error(src + ' is a directory. It must be a file');
  }

  if(src == dst) {

    throw new Error(src + ' and ' + dst + 'are identical');
  }

  var infd = fs.openSync(src, 'r');
  var size = fs.fstatSync(infd).size;
  var outfd = fs.openSync(dst, 'w');

  fs.sendfileSync(outfd, infd, 0, size);

  fs.close(infd);
  fs.close(outfd);
};
<commit_msg>Changed module "sys" to util to get rid of warning; Require module "fs" to fix ReferenceError<commit_after>var sys = require('util');
var events = require('events');
var path = require('path');
var fs = require('fs');

var copy = function copy(src, dst, callback) {
  var self = this;

  if(!callback) {
    callback = function(){};
  }

  self.on('error', function(err) {
    callback(err);
  });

  self.on('validations', function() {

    path.exists(src, function(exists) {

      if(!exists) {

        self.emit('error', new Error(src + ' does not exists. Nothing to be copied'));
        return;
      }

      fs.stat(src, function(err, stat) {

        if(err) {

          self.emit('error', err);
          return;
        }

        if(stat.isDirectory()) {

          self.emit('error', new Error(src + ' is a directory. It must be a file'));
          return;
        }

        if(src == dst) {

          self.emit('error', new Error(src + ' and ' + dst + 'are identical'));
          return;
        }

        self.emit('open_infd');
      });
    });
  });

  self.on('open_infd', function() {

    fs.open(src, 'r', function(err, infd) {

      if(err) {

        self.emit('error', err);
        return;
      }

      self.emit('open_outfd', infd);
    });

  });

  self.on('open_outfd', function(infd) {

    fs.open(dst, 'w', function(err, outfd) {

      if(err) {

        self.emit('error', err);
        return;
      }

      self.emit('sendfile', infd, outfd);
    });
  });

  self.on('sendfile', function(infd, outfd) {

    fs.fstat(infd, function(err, stat) {

      if(err) {

        self.emit('error', err);
        return;
      }

      fs.sendfile(outfd, infd, 0, stat.size, function() {

        self.emit('close_fds', infd, outfd);
        callback();
      });
    });
  });

  self.on('close_fds', function(infd, outfd) {

    fs.close(infd, function(err) {

      if(err) {
        self.emit('error', err);
      }

    });

    fs.close(outfd, function(err) {

      if(err) {

        self.emit('error', err);
      }

    });
  });

  self.emit('validations');
};
sys.inherits(copy, events.EventEmitter);

exports.copy = function(src, dst, callback) {

  return new copy(src, dst, callback);
};

exports.copySync = function copySync(src, dst) {

  if(typeof dst == 'undefined') {

		throw new Error('Destination is not defined');
  }

  if(!path.existsSync(src)) {

    throw new Error(src + ' does not exists.');
  }

  if(fs.statSync(src).isDirectory()) {

    throw new Error(src + ' is a directory. It must be a file');
  }

  if(src == dst) {

    throw new Error(src + ' and ' + dst + 'are identical');
  }

  var infd = fs.openSync(src, 'r');
  var size = fs.fstatSync(infd).size;
  var outfd = fs.openSync(dst, 'w');

  fs.sendfileSync(outfd, infd, 0, size);

  fs.close(infd);
  fs.close(outfd);
};
<|endoftext|>


<commit_before>'use strict';
var core = {};
var mkdirp = require('mkdirp');
var config = require('nconf');
var winston = require('winston');
var CronJob = require('cron').CronJob;
var logger;

var getLoggerSettings = function(consoleLog) {
  var settings = {
    transports: [
      new winston.transports.File({ filename: process.env.HOME + "/.evac/evac.log", json: false})
    ],
    exceptionHandlers: [
      new winston.transports.File({ filename: process.env.HOME + "/.evac/evac.log", json: false})
    ]
  };

  if (consoleLog) {
    settings.transports.push(new winston.transports.Console({colorize: true, timestamp: true}));
    settings.exceptionHandlers.push(new winston.transports.Console({colorize: true, timestamp: true}));
  }

  return settings;
};

var parseRecipeFile = function(path) {
  config.use('file', {file: path});
  config.load(function (err, conf) {
    if (err) {
      logger.error('Failed to load configuration from ' + args[0]);
      process.exit(1);
    }

    core.parseRecipeJson(conf);
  });
};

var parseCronFile = function(path) {
  config.use('file', {file: path});
  config.load(function (err, cronConfig) {
    if (err) {
      logger.error('Failed to load configuration from ' + args[0]);
      process.exit(1);
    }

    var jobs = [];

    for(var config in cronConfig) {
      var oneConfig = cronConfig[config];

      for (var schedule in oneConfig) {
        var job = new CronJob({
          cronTime: schedule,
          onTick: function() {
            parseRecipeFile(oneConfig[schedule]);
          },
          start: true
        });
      }
    }
  });
};

core.parseRecipeJson = function(recipe) {
  var inputPlugin, filterPlugin, outputPlugin,
      inputArgs, filterArgs, outputArgs;

  for (var plugin in recipe.out) {
    var pluginFileName = __dirname + '/plugin/out/' + plugin.toLowerCase() + '.js';
    outputPlugin = require(pluginFileName);
    outputArgs = recipe.out[plugin];
  }

  for (var plugin in recipe.filter) {
    var pluginFileName = __dirname + '/plugin/filter/' + plugin.toLowerCase() + '.js';
    filterPlugin = require(pluginFileName);
    filterArgs = recipe.filter[plugin];
  }

  for (var plugin in recipe.in) {
    var pluginFileName = __dirname + '/plugin/in/' + plugin.toLowerCase() + '.js';
    inputPlugin = require(pluginFileName);
    inputArgs = recipe.in[plugin];
  }

  inputPlugin.load(inputArgs, function(err, outputs){
    if (err) {
      logger.error("Input module - raise error.");
      logger.error(err);
      process.exit(1);
    }

    logger.info("Input plugin called.");

    for (var i=0; i<outputs.length; i++) {
      filterPlugin.filter(filterArgs, outputs[i], function(err, output){
        if (err) {
          logger.error("Filter module - raise error.");
          logger.error(err);
          process.exit(1);
        }

        logger.info("Filter plugin called.");

        if (output == "") {
          logger.info("Filter plugin no output.");
        } else {
          outputPlugin.output(outputArgs, output, function(err, output){
            logger.info("Output plugin received: " + output);
            logger.info("Output finished.");
          });
        }

      });
    }
  });
};

core.start = function(program, args) {
  mkdirp.sync(process.env.HOME + "/.evac");

  logger = new (winston.Logger)(getLoggerSettings(program.verbose));

  if (program.cron) {
    // cron mode
    parseCronFile(args[0]);
  } else {
    // normal mode
    logger.info('Settings are loaded from a file :' + args[0]);
    parseRecipeFile(args[0]);
  }
}

module.exports = core;
<commit_msg>設定ファイル読み込みエラー時の表示パス修正<commit_after>'use strict';
var core = {};
var mkdirp = require('mkdirp');
var config = require('nconf');
var winston = require('winston');
var CronJob = require('cron').CronJob;
var logger;

var getLoggerSettings = function(consoleLog) {
  var settings = {
    transports: [
      new winston.transports.File({ filename: process.env.HOME + "/.evac/evac.log", json: false})
    ],
    exceptionHandlers: [
      new winston.transports.File({ filename: process.env.HOME + "/.evac/evac.log", json: false})
    ]
  };

  if (consoleLog) {
    settings.transports.push(new winston.transports.Console({colorize: true, timestamp: true}));
    settings.exceptionHandlers.push(new winston.transports.Console({colorize: true, timestamp: true}));
  }

  return settings;
};

var parseRecipeFile = function(path) {
  config.use('file', {file: path});
  config.load(function (err, conf) {
    if (err) {
      logger.error('Failed to load configuration from ' + path);
      process.exit(1);
    }

    core.parseRecipeJson(conf);
  });
};

var parseCronFile = function(path) {
  config.use('file', {file: path});
  config.load(function (err, cronConfig) {
    if (err) {
      logger.error('Failed to load configuration from ' + path);
      process.exit(1);
    }

    var jobs = [];

    for(var config in cronConfig) {
      var oneConfig = cronConfig[config];

      for (var schedule in oneConfig) {
        var job = new CronJob({
          cronTime: schedule,
          onTick: function() {
            parseRecipeFile(oneConfig[schedule]);
          },
          start: true
        });
      }
    }
  });
};

core.parseRecipeJson = function(recipe) {
  var inputPlugin, filterPlugin, outputPlugin,
      inputArgs, filterArgs, outputArgs;

  for (var plugin in recipe.out) {
    var pluginFileName = __dirname + '/plugin/out/' + plugin.toLowerCase() + '.js';
    outputPlugin = require(pluginFileName);
    outputArgs = recipe.out[plugin];
  }

  for (var plugin in recipe.filter) {
    var pluginFileName = __dirname + '/plugin/filter/' + plugin.toLowerCase() + '.js';
    filterPlugin = require(pluginFileName);
    filterArgs = recipe.filter[plugin];
  }

  for (var plugin in recipe.in) {
    var pluginFileName = __dirname + '/plugin/in/' + plugin.toLowerCase() + '.js';
    inputPlugin = require(pluginFileName);
    inputArgs = recipe.in[plugin];
  }

  inputPlugin.load(inputArgs, function(err, outputs){
    if (err) {
      logger.error("Input module - raise error.");
      logger.error(err);
      process.exit(1);
    }

    logger.info("Input plugin called.");

    for (var i=0; i<outputs.length; i++) {
      filterPlugin.filter(filterArgs, outputs[i], function(err, output){
        if (err) {
          logger.error("Filter module - raise error.");
          logger.error(err);
          process.exit(1);
        }

        logger.info("Filter plugin called.");

        if (output == "") {
          logger.info("Filter plugin no output.");
        } else {
          outputPlugin.output(outputArgs, output, function(err, output){
            logger.info("Output plugin received: " + output);
            logger.info("Output finished.");
          });
        }

      });
    }
  });
};

core.start = function(program, args) {
  mkdirp.sync(process.env.HOME + "/.evac");

  logger = new (winston.Logger)(getLoggerSettings(program.verbose));

  if (program.cron) {
    // cron mode
    parseCronFile(args[0]);
  } else {
    // normal mode
    logger.info('Settings are loaded from a file :' + args[0]);
    parseRecipeFile(args[0]);
  }
}

module.exports = core;
<|endoftext|>


<commit_before>var absURLRegEx = /^[^\/]+:\/\//;

function readMemberExpression(p, value) {
  var pParts = p.split('.');
  while (pParts.length)
    value = value[pParts.shift()];
  return value;
}

var baseURLCache = {};
function getBaseURLObj() {
  if (baseURLCache[this.baseURL])
    return baseURLCache[this.baseURL];

  // normalize baseURL if not already
  if (this.baseURL[this.baseURL.length - 1] != '/')
    this.baseURL += '/';

  var baseURL = new URL(this.baseURL, baseURI);

  this.baseURL = baseURL.href;

  return (baseURLCache[this.baseURL] = baseURL);
}

var baseURIObj = new URL(baseURI);

(function() {

hookConstructor(function(constructor) {
  return function() {
    constructor.call(this);

    // support baseURL
    this.baseURL = baseURI.substr(0, baseURI.lastIndexOf('/') + 1);

    // support the empty module, as a concept
    this.set('@empty', this.newModule({}));
  };
});

/*
  Normalization

  If a name is relative, we apply URL normalization to the page
  If a name is an absolute URL, we leave it as-is

  Plain names (neither of the above) run through the map and package
  normalization phases (applying before and after this one).

  The paths normalization phase applies last (paths extension), which
  defines the `normalizeSync` function and normalizes everything into
  a URL.

  The final normalization
 */
hook('normalize', function() {
  return function(name, parentName) {
    // relative URL-normalization
    if (name[0] == '.' || name[0] == '/')
      return new URL(name, parentName || baseURIObj).href;
    return name;
  };
});

/*
  __useDefault

  When a module object looks like:
  newModule(
    __useDefault: true,
    default: 'some-module'
  })

  Then importing that module provides the 'some-module'
  result directly instead of the full module.

  Useful for eg module.exports = function() {}
*/
hook('import', function(systemImport) {
  return function(name, parentName, parentAddress) {
    return systemImport.call(this, name, parentName, parentAddress).then(function(module) {
      return module.__useDefault ? module['default'] : module;
    });
  };
});

/*
 Extend config merging one deep only

  loader.config({
    some: 'random',
    config: 'here',
    deep: {
      config: { too: 'too' }
    }
  });

  <=>

  loader.some = 'random';
  loader.config = 'here'
  loader.deep = loader.deep || {};
  loader.deep.config = { too: 'too' };

  Normalizes meta and package configs allowing for:

  System.config({
    meta: {
      './index.js': {}
    }
  });

  To become

  System.meta['https://thissite.com/index.js'] = {};

  For easy normalization canonicalization with latest URL support.

*/
SystemJSLoader.prototype.config = function(cfg) {

  // always configure baseURL first
  if (cfg.baseURL) {
    var hasConfig = false;
    function checkHasConfig(obj) {
      for (var p in obj)
        return true;
    }
    if (checkHasConfig(this.packages) || checkHasConfig(this.meta) || checkHasConfig(this.depCache) || checkHasConfig(this.bundles))
      throw new TypeError('baseURL should only be configured once and must be configured first.');

    this.baseURL = cfg.baseURL;

    // sanitize baseURL
    getBaseURLObj.call(this);
  }

  if (cfg.map) {
    for (var p in cfg.map) {
      var v = cfg.map[p];

      // object map backwards-compat into packages configuration
      if (typeof v !== 'string') {
        var normalized = this.normalizeSync(p);

        // if doing default js extensions, undo to get package name
        if (this.defaultJSExtensions && p.substr(p.length - 3, 3) != '.js')
          normalized = normalized.substr(0, normalized.length - 3);

        // if a package main, revert it
        var pkgMatch = '';
        for (var pkg in this.packages) {
          if (normalized.substr(0, pkg.length) == pkg
              && (!normalized[pkg.length] || normalized[pkg.length] == '/')
              && pkgMatch.split('/').length < pkg.split('/').length)
            pkgMatch = pkg;
        }
        if (pkgMatch && this.packages[pkgMatch].main)
          normalized = normalized.substr(0, normalized.length - this.packages[pkgMatch].main.length - 1);

        var pkg = this.packages[normalized] = this.packages[normalized] || {};
        pkg.map = v;
      }
      else {
        this.map[p] = v;
      }
    }
  }

  if (cfg.packages) {
    for (var p in cfg.packages) {
      var prop = this.normalizeSync(p);

      // if doing default js extensions, undo to get package name
      if (this.defaultJSExtensions && p.substr(p.length - 3, 3) != '.js')
        prop = prop.substr(0, prop.length - 3);

      this.packages[prop]= this.packages[prop] || {};
      for (var q in cfg.packages[p])
        this.packages[prop][q] = cfg.packages[p][q];
    }
  }

  if (cfg.bundles) {
    for (var p in cfg.bundles) {
      var bundle = [];
      for (var i = 0; i < cfg.bundles[p].length; i++)
        bundle.push(this.normalizeSync(cfg.bundles[p][i]));
      this.bundles[p] = bundle;
    }
  }

  for (var c in cfg) {
    var v = cfg[c];
    var normalizeProp = false, normalizeValArray = false;

    if (c == 'baseURL' || c == 'map' || c == 'packages' || c == 'bundles')
      continue;

    if (typeof v != 'object' || v instanceof Array) {
      this[c] = v;
    }
    else {
      this[c] = this[c] || {};

      if (c == 'meta' || c == 'depCache')
        normalizeProp = true;

      for (var p in v) {
        if (normalizeProp)
          this[c][this.normalizeSync(p)] = v[p];
        else
          this[c][p] = v[p];
      }
    }
  }
};

})();<commit_msg>paths apply first in config<commit_after>var absURLRegEx = /^[^\/]+:\/\//;

function readMemberExpression(p, value) {
  var pParts = p.split('.');
  while (pParts.length)
    value = value[pParts.shift()];
  return value;
}

var baseURLCache = {};
function getBaseURLObj() {
  if (baseURLCache[this.baseURL])
    return baseURLCache[this.baseURL];

  // normalize baseURL if not already
  if (this.baseURL[this.baseURL.length - 1] != '/')
    this.baseURL += '/';

  var baseURL = new URL(this.baseURL, baseURI);

  this.baseURL = baseURL.href;

  return (baseURLCache[this.baseURL] = baseURL);
}

var baseURIObj = new URL(baseURI);

(function() {

hookConstructor(function(constructor) {
  return function() {
    constructor.call(this);

    // support baseURL
    this.baseURL = baseURI.substr(0, baseURI.lastIndexOf('/') + 1);

    // support the empty module, as a concept
    this.set('@empty', this.newModule({}));
  };
});

/*
  Normalization

  If a name is relative, we apply URL normalization to the page
  If a name is an absolute URL, we leave it as-is

  Plain names (neither of the above) run through the map and package
  normalization phases (applying before and after this one).

  The paths normalization phase applies last (paths extension), which
  defines the `normalizeSync` function and normalizes everything into
  a URL.

  The final normalization
 */
hook('normalize', function() {
  return function(name, parentName) {
    // relative URL-normalization
    if (name[0] == '.' || name[0] == '/')
      return new URL(name, parentName || baseURIObj).href;
    return name;
  };
});

/*
  __useDefault

  When a module object looks like:
  newModule(
    __useDefault: true,
    default: 'some-module'
  })

  Then importing that module provides the 'some-module'
  result directly instead of the full module.

  Useful for eg module.exports = function() {}
*/
hook('import', function(systemImport) {
  return function(name, parentName, parentAddress) {
    return systemImport.call(this, name, parentName, parentAddress).then(function(module) {
      return module.__useDefault ? module['default'] : module;
    });
  };
});

/*
 Extend config merging one deep only

  loader.config({
    some: 'random',
    config: 'here',
    deep: {
      config: { too: 'too' }
    }
  });

  <=>

  loader.some = 'random';
  loader.config = 'here'
  loader.deep = loader.deep || {};
  loader.deep.config = { too: 'too' };

  Normalizes meta and package configs allowing for:

  System.config({
    meta: {
      './index.js': {}
    }
  });

  To become

  System.meta['https://thissite.com/index.js'] = {};

  For easy normalization canonicalization with latest URL support.

*/
SystemJSLoader.prototype.config = function(cfg) {

  // always configure baseURL first
  if (cfg.baseURL) {
    var hasConfig = false;
    function checkHasConfig(obj) {
      for (var p in obj)
        return true;
    }
    if (checkHasConfig(this.packages) || checkHasConfig(this.meta) || checkHasConfig(this.depCache) || checkHasConfig(this.bundles))
      throw new TypeError('baseURL should only be configured once and must be configured first.');

    this.baseURL = cfg.baseURL;

    // sanitize baseURL
    getBaseURLObj.call(this);
  }

  if (cfg.paths) {
    for (var p in cfg.paths)
      this.paths[p] = cfg.paths[p];
  }

  if (cfg.map) {
    for (var p in cfg.map) {
      var v = cfg.map[p];

      // object map backwards-compat into packages configuration
      if (typeof v !== 'string') {
        var normalized = this.normalizeSync(p);

        // if doing default js extensions, undo to get package name
        if (this.defaultJSExtensions && p.substr(p.length - 3, 3) != '.js')
          normalized = normalized.substr(0, normalized.length - 3);

        // if a package main, revert it
        var pkgMatch = '';
        for (var pkg in this.packages) {
          if (normalized.substr(0, pkg.length) == pkg
              && (!normalized[pkg.length] || normalized[pkg.length] == '/')
              && pkgMatch.split('/').length < pkg.split('/').length)
            pkgMatch = pkg;
        }
        if (pkgMatch && this.packages[pkgMatch].main)
          normalized = normalized.substr(0, normalized.length - this.packages[pkgMatch].main.length - 1);

        var pkg = this.packages[normalized] = this.packages[normalized] || {};
        pkg.map = v;
      }
      else {
        this.map[p] = v;
      }
    }
  }

  if (cfg.packages) {
    for (var p in cfg.packages) {
      var prop = this.normalizeSync(p);

      // if doing default js extensions, undo to get package name
      if (this.defaultJSExtensions && p.substr(p.length - 3, 3) != '.js')
        prop = prop.substr(0, prop.length - 3);

      this.packages[prop]= this.packages[prop] || {};
      for (var q in cfg.packages[p])
        this.packages[prop][q] = cfg.packages[p][q];
    }
  }

  if (cfg.bundles) {
    for (var p in cfg.bundles) {
      var bundle = [];
      for (var i = 0; i < cfg.bundles[p].length; i++)
        bundle.push(this.normalizeSync(cfg.bundles[p][i]));
      this.bundles[p] = bundle;
    }
  }

  for (var c in cfg) {
    var v = cfg[c];
    var normalizeProp = false, normalizeValArray = false;

    if (c == 'baseURL' || c == 'map' || c == 'packages' || c == 'bundles')
      continue;

    if (typeof v != 'object' || v instanceof Array) {
      this[c] = v;
    }
    else {
      this[c] = this[c] || {};

      if (c == 'meta' || c == 'depCache')
        normalizeProp = true;

      for (var p in v) {
        if (normalizeProp)
          this[c][this.normalizeSync(p)] = v[p];
        else
          this[c][p] = v[p];
      }
    }
  }
};

})();<|endoftext|>