<commit_before>'use strict';

require('co-mocha')(require('mocha')); // monkey patch mocha for generators

const config = require('config');
const nodemailer = require('nodemailer');
const Email = require('../../src/email/email');
const Mongo = require('../../src/dao/mongo');
const PGP = require('../../src/service/pgp');
const PublicKey = require('../../src/service/public-key');
const expect = require('chai').expect;
const sinon = require('sinon');

describe('Public Key Integration Tests', function() {
  this.timeout(20000);

  let publicKey, email, mongo, pgp,
    sendEmailStub, publicKeyArmored, publicKeyArmored2, mailsSent;

  const DB_TYPE = 'publickey';
  const primaryEmail = 'test1@example.com';
  const primaryEmail2 = 'test2@example.com';
  const origin = { host:'localhost', protocol:'http' };

  before(function *() {
    publicKeyArmored = require('fs').readFileSync(__dirname + '/../key3.asc', 'utf8');
    publicKeyArmored2 = require('fs').readFileSync(__dirname + '/../key4.asc', 'utf8');
    mongo = new Mongo();
    yield mongo.init(config.mongo);
  });

  beforeEach(function *() {
    yield mongo.clear(DB_TYPE);
    mailsSent = [];
    sendEmailStub = sinon.stub().returns(Promise.resolve({ response:'250' }));
    sendEmailStub.withArgs(sinon.match(recipient => {
      mailsSent[mailsSent.length] = {to:recipient.to.address};
      return true;
    }), sinon.match(params => {
      mailsSent[mailsSent.length - 1].params = params;
      expect(params.nonce).to.exist;
      expect(params.keyId).to.exist;
      return true;
    }));
    sinon.stub(nodemailer, 'createTransport').returns({
      templateSender: () => { return sendEmailStub; }
    });
    email = new Email(nodemailer);
    email.init({
      host: 'localhost',
      auth: { user:'user', pass:'pass' },
      sender: { name:'Foo Bar', email:'foo@bar.com' }
    });
    pgp = new PGP();
    publicKey = new PublicKey(pgp, mongo, email);
  });

  afterEach(() => {
    nodemailer.createTransport.restore();
  });

  after(function *() {
    yield mongo.clear(DB_TYPE);
    yield mongo.disconnect();
  });

  describe('put', () => {
    it('should persist key and send verification email with primaryEmail', function *() {
      yield publicKey.put({ publicKeyArmored, primaryEmail, origin });
      expect(mailsSent.length).to.equal(1);
      expect(mailsSent[0].to).to.equal(primaryEmail);
      expect(mailsSent[0].params.keyId).to.exist;
      expect(mailsSent[0].params.nonce).to.exist;
    });
    it('should persist key and send verification email without primaryEmail', function *() {
      yield publicKey.put({ publicKeyArmored, origin });
      expect(mailsSent.length).to.equal(4);
    });

    it('should work twice if not yet verified', function *() {
      yield publicKey.put({ publicKeyArmored, primaryEmail, origin });
      expect(mailsSent.length).to.equal(1);
      yield publicKey.put({ publicKeyArmored, primaryEmail, origin });
      expect(mailsSent.length).to.equal(2);
    });

    it('should throw 304 if key already exists', function *() {
      yield publicKey.put({ publicKeyArmored, primaryEmail, origin });
      yield publicKey.verify(mailsSent[0].params);
      try {
        yield publicKey.put({ publicKeyArmored, primaryEmail, origin });
        expect(false).to.be.true;
      } catch(e) {
        expect(e.status).to.equal(304);
      }
    });
  });

  describe('verify', () => {
    it('should update the document', function *() {
      yield publicKey.put({ publicKeyArmored, primaryEmail, origin });
      let emailParams = mailsSent[0].params;
      yield publicKey.verify(emailParams);
      let gotten = yield mongo.get({ keyId:emailParams.keyId }, DB_TYPE);
      expect(gotten.userIds[0].verified).to.be.true;
      expect(gotten.userIds[0].nonce).to.be.null;
      expect(gotten.userIds[1].verified).to.be.false;
      expect(gotten.userIds[1].nonce).to.exist;
    });

    it('should not find the document', function *() {
      yield publicKey.put({ publicKeyArmored, primaryEmail, origin });
      let emailParams = mailsSent[0].params;
      try {
        yield publicKey.verify({ keyId:emailParams.keyId, nonce:'fake_nonce' });
        expect(true).to.be.false;
      } catch(e) {
        expect(e.status).to.equal(404);
      }
      let gotten = yield mongo.get({ keyId:emailParams.keyId }, DB_TYPE);
      expect(gotten.userIds[0].verified).to.be.false;
      expect(gotten.userIds[0].nonce).to.equal(emailParams.nonce);
      expect(gotten.userIds[1].verified).to.be.false;
      expect(gotten.userIds[1].nonce).to.exist;
    });

    it('should not verify a second key for already verified user id of another key', function *() {
      yield publicKey.put({ publicKeyArmored, primaryEmail:primaryEmail2, origin });
      expect(mailsSent.length).to.equal(1);
      yield publicKey.put({ publicKeyArmored:publicKeyArmored2, primaryEmail:primaryEmail2, origin });
      expect(mailsSent.length).to.equal(2);
      yield publicKey.verify(mailsSent[1].params);

      try {
        yield publicKey.verify(mailsSent[0].params);
        expect(true).to.be.false;
      } catch(e) {
        expect(e.status).to.equal(304);
      }
      let gotten = yield mongo.get({ keyId:mailsSent[0].params.keyId }, DB_TYPE);
      expect(gotten.userIds[1].email).to.equal(primaryEmail2);
      expect(gotten.userIds[1].verified).to.be.false;
      expect(gotten.userIds[1].nonce).to.equal(mailsSent[0].params.nonce);
    });

    it('should be able to verify multiple user ids', function *() {
      yield publicKey.put({ publicKeyArmored, origin });
      expect(mailsSent.length).to.equal(4);
      yield publicKey.verify(mailsSent[0].params);
      yield publicKey.verify(mailsSent[1].params);
      yield publicKey.verify(mailsSent[2].params);
      yield publicKey.verify(mailsSent[3].params);
      let gotten = yield mongo.get({ keyId:mailsSent[0].params.keyId }, DB_TYPE);
      expect(gotten.userIds[0].verified).to.be.true;
      expect(gotten.userIds[1].verified).to.be.true;
      expect(gotten.userIds[2].verified).to.be.true;
      expect(gotten.userIds[3].verified).to.be.true;
    });

  });

  describe('getVerified', () => {
    let key;

    describe('should find a verified key', () => {
      beforeEach(function *() {
        key = pgp.parseKey(publicKeyArmored);
        yield publicKey.put({ publicKeyArmored, primaryEmail, origin });
        yield publicKey.verify(mailsSent[0].params);
      });

      it('by fingerprint', function *() {
        let verified = yield publicKey.getVerified({ fingerprint:key.fingerprint });
        expect(verified).to.exist;
      });

      it('by all userIds', function *() {
        let verified = yield publicKey.getVerified({ userIds:key.userIds });
        expect(verified).to.exist;
      });

      it('by verified userId', function *() {
        let verified = yield publicKey.getVerified({ userIds:[key.userIds[0]] });
        expect(verified).to.exist;
      });

      it('by unverified userId', function *() {
        let verified = yield publicKey.getVerified({ userIds:[key.userIds[1]] });
        expect(verified).to.not.exist;
      });

      it('by keyId', function *() {
        let verified = yield publicKey.getVerified({ keyId:key.keyId });
        expect(verified).to.exist;
      });

      it('by all params', function *() {
        let verified = yield publicKey.getVerified(key);
        expect(verified).to.exist;
      });
    });

    describe('should not find an unverified key', () => {
      beforeEach(function *() {
        key = pgp.parseKey(publicKeyArmored);
        key.userIds[0].verified = false;
        yield mongo.create(key, DB_TYPE);
      });

      it('by fingerprint', function *() {
        let verified = yield publicKey.getVerified({ fingerprint:key.fingerprint });
        expect(verified).to.not.exist;
      });

      it('by userIds', function *() {
        let verified = yield publicKey.getVerified({ userIds:key.userIds });
        expect(verified).to.not.exist;
      });

      it('by keyId', function *() {
        let verified = yield publicKey.getVerified({ keyId:key.keyId });
        expect(verified).to.not.exist;
      });

      it('by all params', function *() {
        let verified = yield publicKey.getVerified(key);
        expect(verified).to.not.exist;
      });
    });
  });

  describe('get', () => {
    let emailParams;

    beforeEach(function *() {
      yield publicKey.put({ publicKeyArmored, primaryEmail, origin });
      emailParams = mailsSent[0].params;
    });

    it('should return verified key by key id', function *() {
      yield publicKey.verify(emailParams);
      let key = yield publicKey.get({ keyId:emailParams.keyId });
      expect(key.publicKeyArmored).to.exist;
    });

    it('should return verified key by key id (uppercase)', function *() {
      yield publicKey.verify(emailParams);
      let key = yield publicKey.get({ keyId:emailParams.keyId.toUpperCase() });
      expect(key.publicKeyArmored).to.exist;
    });

    it('should return verified key by fingerprint', function *() {
      yield publicKey.verify(emailParams);
      let fingerprint = pgp.parseKey(publicKeyArmored).fingerprint;
      let key = yield publicKey.get({ fingerprint });
      expect(key.publicKeyArmored).to.exist;
    });

    it('should return verified key by fingerprint (uppercase)', function *() {
      yield publicKey.verify(emailParams);
      let fingerprint = pgp.parseKey(publicKeyArmored).fingerprint.toUpperCase();
      let key = yield publicKey.get({ fingerprint });
      expect(key.publicKeyArmored).to.exist;
    });

    it('should return verified key by email address', function *() {
      yield publicKey.verify(emailParams);
      let key = yield publicKey.get({ email:primaryEmail });
      expect(key.publicKeyArmored).to.exist;
    });

    it('should return verified key by email address (uppercase)', function *() {
      yield publicKey.verify(emailParams);
      let key = yield publicKey.get({ email:primaryEmail.toUpperCase() });
      expect(key.publicKeyArmored).to.exist;
    });

    it('should throw 404 for unverified key', function *() {
      try {
        yield publicKey.get({ keyId:emailParams.keyId });
        expect(false).to.be.true;
      } catch(e) {
        expect(e.status).to.equal(404);
      }
    });
  });

  describe('requestRemove', () => {
    let keyId;

    beforeEach(function *() {
      yield publicKey.put({ publicKeyArmored, primaryEmail, origin });
      keyId = mailsSent[0].params.keyId;
    });

    it('should work for verified key', function *() {
      yield publicKey.verify(mailsSent[0].params);
      yield publicKey.requestRemove({ keyId, origin });
      expect(mailsSent.length).to.equal(5);
    });

    it('should work for unverified key', function *() {
      yield publicKey.requestRemove({ keyId, origin });
      expect(mailsSent.length).to.equal(5);
    });

    it('should work by email address', function *() {
      yield publicKey.requestRemove({ email:primaryEmail, origin });
      expect(mailsSent.length).to.equal(2);
    });

    it('should throw 404 for no key', function *() {
      yield mongo.remove({ keyId }, DB_TYPE);
      try {
        yield publicKey.requestRemove({ keyId, origin });
        expect(false).to.be.true;
      } catch(e) {
        expect(e.status).to.equal(404);
      }
    });
  });

  describe('verifyRemove', () => {
    let keyId;

    beforeEach(function *() {
      yield publicKey.put({ publicKeyArmored, primaryEmail, origin });
      keyId = mailsSent[0].params.keyId;
      yield publicKey.requestRemove({ keyId, origin });
    });

    it('should remove key', function *() {
      yield publicKey.verifyRemove(mailsSent[1].params);
      let key = yield mongo.get({ keyId }, DB_TYPE);
      expect(key).to.not.exist;
    });

    it('should throw 404 for no key', function *() {
      yield mongo.remove({ keyId }, DB_TYPE);
      try {
        yield publicKey.verifyRemove(mailsSent[1].params);
        expect(false).to.be.true;
      } catch(e) {
        expect(e.status).to.equal(404);
      }
    });
  });

});<commit_msg>Remve empty line in test<commit_after>'use strict';

require('co-mocha')(require('mocha')); // monkey patch mocha for generators

const config = require('config');
const nodemailer = require('nodemailer');
const Email = require('../../src/email/email');
const Mongo = require('../../src/dao/mongo');
const PGP = require('../../src/service/pgp');
const PublicKey = require('../../src/service/public-key');
const expect = require('chai').expect;
const sinon = require('sinon');

describe('Public Key Integration Tests', function() {
  this.timeout(20000);

  let publicKey, email, mongo, pgp,
    sendEmailStub, publicKeyArmored, publicKeyArmored2, mailsSent;

  const DB_TYPE = 'publickey';
  const primaryEmail = 'test1@example.com';
  const primaryEmail2 = 'test2@example.com';
  const origin = { host:'localhost', protocol:'http' };

  before(function *() {
    publicKeyArmored = require('fs').readFileSync(__dirname + '/../key3.asc', 'utf8');
    publicKeyArmored2 = require('fs').readFileSync(__dirname + '/../key4.asc', 'utf8');
    mongo = new Mongo();
    yield mongo.init(config.mongo);
  });

  beforeEach(function *() {
    yield mongo.clear(DB_TYPE);
    mailsSent = [];
    sendEmailStub = sinon.stub().returns(Promise.resolve({ response:'250' }));
    sendEmailStub.withArgs(sinon.match(recipient => {
      mailsSent[mailsSent.length] = {to:recipient.to.address};
      return true;
    }), sinon.match(params => {
      mailsSent[mailsSent.length - 1].params = params;
      expect(params.nonce).to.exist;
      expect(params.keyId).to.exist;
      return true;
    }));
    sinon.stub(nodemailer, 'createTransport').returns({
      templateSender: () => { return sendEmailStub; }
    });
    email = new Email(nodemailer);
    email.init({
      host: 'localhost',
      auth: { user:'user', pass:'pass' },
      sender: { name:'Foo Bar', email:'foo@bar.com' }
    });
    pgp = new PGP();
    publicKey = new PublicKey(pgp, mongo, email);
  });

  afterEach(() => {
    nodemailer.createTransport.restore();
  });

  after(function *() {
    yield mongo.clear(DB_TYPE);
    yield mongo.disconnect();
  });

  describe('put', () => {
    it('should persist key and send verification email with primaryEmail', function *() {
      yield publicKey.put({ publicKeyArmored, primaryEmail, origin });
      expect(mailsSent.length).to.equal(1);
      expect(mailsSent[0].to).to.equal(primaryEmail);
      expect(mailsSent[0].params.keyId).to.exist;
      expect(mailsSent[0].params.nonce).to.exist;
    });
    it('should persist key and send verification email without primaryEmail', function *() {
      yield publicKey.put({ publicKeyArmored, origin });
      expect(mailsSent.length).to.equal(4);
    });

    it('should work twice if not yet verified', function *() {
      yield publicKey.put({ publicKeyArmored, primaryEmail, origin });
      expect(mailsSent.length).to.equal(1);
      yield publicKey.put({ publicKeyArmored, primaryEmail, origin });
      expect(mailsSent.length).to.equal(2);
    });

    it('should throw 304 if key already exists', function *() {
      yield publicKey.put({ publicKeyArmored, primaryEmail, origin });
      yield publicKey.verify(mailsSent[0].params);
      try {
        yield publicKey.put({ publicKeyArmored, primaryEmail, origin });
        expect(false).to.be.true;
      } catch(e) {
        expect(e.status).to.equal(304);
      }
    });
  });

  describe('verify', () => {
    it('should update the document', function *() {
      yield publicKey.put({ publicKeyArmored, primaryEmail, origin });
      let emailParams = mailsSent[0].params;
      yield publicKey.verify(emailParams);
      let gotten = yield mongo.get({ keyId:emailParams.keyId }, DB_TYPE);
      expect(gotten.userIds[0].verified).to.be.true;
      expect(gotten.userIds[0].nonce).to.be.null;
      expect(gotten.userIds[1].verified).to.be.false;
      expect(gotten.userIds[1].nonce).to.exist;
    });

    it('should not find the document', function *() {
      yield publicKey.put({ publicKeyArmored, primaryEmail, origin });
      let emailParams = mailsSent[0].params;
      try {
        yield publicKey.verify({ keyId:emailParams.keyId, nonce:'fake_nonce' });
        expect(true).to.be.false;
      } catch(e) {
        expect(e.status).to.equal(404);
      }
      let gotten = yield mongo.get({ keyId:emailParams.keyId }, DB_TYPE);
      expect(gotten.userIds[0].verified).to.be.false;
      expect(gotten.userIds[0].nonce).to.equal(emailParams.nonce);
      expect(gotten.userIds[1].verified).to.be.false;
      expect(gotten.userIds[1].nonce).to.exist;
    });

    it('should not verify a second key for already verified user id of another key', function *() {
      yield publicKey.put({ publicKeyArmored, primaryEmail:primaryEmail2, origin });
      expect(mailsSent.length).to.equal(1);
      yield publicKey.put({ publicKeyArmored:publicKeyArmored2, primaryEmail:primaryEmail2, origin });
      expect(mailsSent.length).to.equal(2);
      yield publicKey.verify(mailsSent[1].params);

      try {
        yield publicKey.verify(mailsSent[0].params);
        expect(true).to.be.false;
      } catch(e) {
        expect(e.status).to.equal(304);
      }
      let gotten = yield mongo.get({ keyId:mailsSent[0].params.keyId }, DB_TYPE);
      expect(gotten.userIds[1].email).to.equal(primaryEmail2);
      expect(gotten.userIds[1].verified).to.be.false;
      expect(gotten.userIds[1].nonce).to.equal(mailsSent[0].params.nonce);
    });

    it('should be able to verify multiple user ids', function *() {
      yield publicKey.put({ publicKeyArmored, origin });
      expect(mailsSent.length).to.equal(4);
      yield publicKey.verify(mailsSent[0].params);
      yield publicKey.verify(mailsSent[1].params);
      yield publicKey.verify(mailsSent[2].params);
      yield publicKey.verify(mailsSent[3].params);
      let gotten = yield mongo.get({ keyId:mailsSent[0].params.keyId }, DB_TYPE);
      expect(gotten.userIds[0].verified).to.be.true;
      expect(gotten.userIds[1].verified).to.be.true;
      expect(gotten.userIds[2].verified).to.be.true;
      expect(gotten.userIds[3].verified).to.be.true;
    });
  });

  describe('getVerified', () => {
    let key;

    describe('should find a verified key', () => {
      beforeEach(function *() {
        key = pgp.parseKey(publicKeyArmored);
        yield publicKey.put({ publicKeyArmored, primaryEmail, origin });
        yield publicKey.verify(mailsSent[0].params);
      });

      it('by fingerprint', function *() {
        let verified = yield publicKey.getVerified({ fingerprint:key.fingerprint });
        expect(verified).to.exist;
      });

      it('by all userIds', function *() {
        let verified = yield publicKey.getVerified({ userIds:key.userIds });
        expect(verified).to.exist;
      });

      it('by verified userId', function *() {
        let verified = yield publicKey.getVerified({ userIds:[key.userIds[0]] });
        expect(verified).to.exist;
      });

      it('by unverified userId', function *() {
        let verified = yield publicKey.getVerified({ userIds:[key.userIds[1]] });
        expect(verified).to.not.exist;
      });

      it('by keyId', function *() {
        let verified = yield publicKey.getVerified({ keyId:key.keyId });
        expect(verified).to.exist;
      });

      it('by all params', function *() {
        let verified = yield publicKey.getVerified(key);
        expect(verified).to.exist;
      });
    });

    describe('should not find an unverified key', () => {
      beforeEach(function *() {
        key = pgp.parseKey(publicKeyArmored);
        key.userIds[0].verified = false;
        yield mongo.create(key, DB_TYPE);
      });

      it('by fingerprint', function *() {
        let verified = yield publicKey.getVerified({ fingerprint:key.fingerprint });
        expect(verified).to.not.exist;
      });

      it('by userIds', function *() {
        let verified = yield publicKey.getVerified({ userIds:key.userIds });
        expect(verified).to.not.exist;
      });

      it('by keyId', function *() {
        let verified = yield publicKey.getVerified({ keyId:key.keyId });
        expect(verified).to.not.exist;
      });

      it('by all params', function *() {
        let verified = yield publicKey.getVerified(key);
        expect(verified).to.not.exist;
      });
    });
  });

  describe('get', () => {
    let emailParams;

    beforeEach(function *() {
      yield publicKey.put({ publicKeyArmored, primaryEmail, origin });
      emailParams = mailsSent[0].params;
    });

    it('should return verified key by key id', function *() {
      yield publicKey.verify(emailParams);
      let key = yield publicKey.get({ keyId:emailParams.keyId });
      expect(key.publicKeyArmored).to.exist;
    });

    it('should return verified key by key id (uppercase)', function *() {
      yield publicKey.verify(emailParams);
      let key = yield publicKey.get({ keyId:emailParams.keyId.toUpperCase() });
      expect(key.publicKeyArmored).to.exist;
    });

    it('should return verified key by fingerprint', function *() {
      yield publicKey.verify(emailParams);
      let fingerprint = pgp.parseKey(publicKeyArmored).fingerprint;
      let key = yield publicKey.get({ fingerprint });
      expect(key.publicKeyArmored).to.exist;
    });

    it('should return verified key by fingerprint (uppercase)', function *() {
      yield publicKey.verify(emailParams);
      let fingerprint = pgp.parseKey(publicKeyArmored).fingerprint.toUpperCase();
      let key = yield publicKey.get({ fingerprint });
      expect(key.publicKeyArmored).to.exist;
    });

    it('should return verified key by email address', function *() {
      yield publicKey.verify(emailParams);
      let key = yield publicKey.get({ email:primaryEmail });
      expect(key.publicKeyArmored).to.exist;
    });

    it('should return verified key by email address (uppercase)', function *() {
      yield publicKey.verify(emailParams);
      let key = yield publicKey.get({ email:primaryEmail.toUpperCase() });
      expect(key.publicKeyArmored).to.exist;
    });

    it('should throw 404 for unverified key', function *() {
      try {
        yield publicKey.get({ keyId:emailParams.keyId });
        expect(false).to.be.true;
      } catch(e) {
        expect(e.status).to.equal(404);
      }
    });
  });

  describe('requestRemove', () => {
    let keyId;

    beforeEach(function *() {
      yield publicKey.put({ publicKeyArmored, primaryEmail, origin });
      keyId = mailsSent[0].params.keyId;
    });

    it('should work for verified key', function *() {
      yield publicKey.verify(mailsSent[0].params);
      yield publicKey.requestRemove({ keyId, origin });
      expect(mailsSent.length).to.equal(5);
    });

    it('should work for unverified key', function *() {
      yield publicKey.requestRemove({ keyId, origin });
      expect(mailsSent.length).to.equal(5);
    });

    it('should work by email address', function *() {
      yield publicKey.requestRemove({ email:primaryEmail, origin });
      expect(mailsSent.length).to.equal(2);
    });

    it('should throw 404 for no key', function *() {
      yield mongo.remove({ keyId }, DB_TYPE);
      try {
        yield publicKey.requestRemove({ keyId, origin });
        expect(false).to.be.true;
      } catch(e) {
        expect(e.status).to.equal(404);
      }
    });
  });

  describe('verifyRemove', () => {
    let keyId;

    beforeEach(function *() {
      yield publicKey.put({ publicKeyArmored, primaryEmail, origin });
      keyId = mailsSent[0].params.keyId;
      yield publicKey.requestRemove({ keyId, origin });
    });

    it('should remove key', function *() {
      yield publicKey.verifyRemove(mailsSent[1].params);
      let key = yield mongo.get({ keyId }, DB_TYPE);
      expect(key).to.not.exist;
    });

    it('should throw 404 for no key', function *() {
      yield mongo.remove({ keyId }, DB_TYPE);
      try {
        yield publicKey.verifyRemove(mailsSent[1].params);
        expect(false).to.be.true;
      } catch(e) {
        expect(e.status).to.equal(404);
      }
    });
  });

});<|endoftext|>


<commit_before>/* Copyright 2015 Open Ag Data Alliance
 *
 * 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.
 */

'use strict';

var chai = require('chai');
chai.use(require('chai-as-promised'));
var expect = chai.expect;

var fs = require('fs');
var jws = require('jws');
var jwt = require('jsonwebtoken');
var rsaPemToJwk = require('rsa-pem-to-jwk');
var clientAuth = require('../');

describe('exports', function() {
    ['generate', 'verify'].forEach(function(method) {
        it('should export ' + method, function() {
            expect(clientAuth[method]).to.be.a('function');
        });
    });
});

describe('generate', function() {
    var privatePem;
    var publicPem;
    var publicJwk;
    var expiresIn;
    var issuer;
    var clientId;
    var tokenEndpoint;

    function checkToken(token, cb) {
        var decoded = jws.decode(token);
        expect(decoded.header.kid).to.equal(privatePem.kid);
        expect(decoded.payload.exp - decoded.payload.iat).to.equal(expiresIn);
        expect(decoded.payload.iss).to.equal(issuer);
        expect(decoded.payload.sub).to.equal(clientId);
        expect(decoded.payload.aud).to.equal(tokenEndpoint);

        jwt.verify(token, publicPem.toString(), function(err) {
            expect(err).to.not.be.ok;

            cb(decoded);
        });
    }

    beforeEach(function() {
        privatePem = {
            kid: 'abc123',
            kty: 'PEM',
            pem: fs.readFileSync('test/keys/abc123.private.pem')
        };
        publicPem = fs.readFileSync('test/keys/abc123.public.pem');
        publicJwk = rsaPemToJwk(publicPem, {kid: 'abc123'});
        expiresIn = 123;
        issuer = 'fJd7s723qa';
        clientId = 'xi7sca3';
        tokenEndpoint = 'https://example.org/token';
    });

    it('should require object for key', function() {
        var token = clientAuth.generate();

        expect(token).to.equal(undefined);
    });

    it('should require a PEM key', function() {
        var token = clientAuth.generate({kty: 'RSA'});

        expect(token).to.equal(undefined);
    });

    it('should require issuer to be a string', function() {
        var token = clientAuth.generate(privatePem, expiresIn);

        expect(token).to.equal(undefined);
    });

    it('should require clientId to be a string', function() {
        var token = clientAuth.generate(privatePem, issuer, expiresIn);

        expect(token).to.equal(undefined);
    });

    it('should require tokenEndpoint to be a string', function() {
        var token = clientAuth.generate(privatePem, issuer, clientId,
            expiresIn);

        expect(token).to.equal(undefined);
    });

    it('should require expires to be a number', function() {
        var token = clientAuth.generate(privatePem, issuer, clientId,
            tokenEndpoint, 'expiresIn');

        expect(token).to.equal(undefined);
    });

    it('should require options to be an object', function() {
        var token = clientAuth.generate(privatePem, issuer, clientId,
            tokenEndpoint, expiresIn, 'options');

        expect(token).to.equal(undefined);
    });

    it('should allow no options', function(done) {
        var token = clientAuth.generate(privatePem, issuer, clientId,
            tokenEndpoint, expiresIn);

        checkToken(token, function() {
            done();
        });
    });

    it('should not allow overwriting required parameters', function(done) {
        var options = {
            algorithm: 'RS256' + 'invalid',
            issuer: issuer + 'invalid',
            subject: clientId + 'invalid',
            audience: tokenEndpoint + 'invalid',
            expiresInSeconds: expiresIn + 1000
        };

        var token = clientAuth.generate(privatePem, issuer, clientId,
            tokenEndpoint, expiresIn, options);

        checkToken(token, function() {
            done();
        });
    });

    it('should allow other payload claims', function(done) {
        var options = {
            payload: {
                jti: 'JTI'
            }
        };

        var token = clientAuth.generate(privatePem, issuer, clientId,
            tokenEndpoint, expiresIn, options);

        checkToken(token, function(decoded) {
            expect(decoded.payload.jti).to.equal('JTI');

            done();
        });
    });

    it('should allow a signing key with an id', function(done) {
        delete privatePem.kid;

        var token = clientAuth.generate(privatePem, issuer, clientId,
            tokenEndpoint, expiresIn);

        checkToken(token, function(decoded) {
            expect(decoded.header.kid).to.equal(undefined);

            done();
        });
    });
});

describe('verify', function() {
    var privatePem = {
        kid: 'abc123',
        kty: 'PEM',
        pem: fs.readFileSync('test/keys/abc123.private.pem')
    };
    var publicPem = fs.readFileSync('test/keys/abc123.public.pem');
    var publicJwk = rsaPemToJwk(publicPem, {kid: 'abc123'});
    var options;

    beforeEach(function() {
        options = {
            algorithm: 'RS256',
            issuer: 'Xjsi3f93',
            subject: 'vmaAU93F',
            audience: 'https://api.example.org/token',
            expiresInSeconds: 10,
            headers: {
                kid: privatePem.kid
            }
        };
    });

    it('should verify a valid token', function() {
        var token = jwt.sign({}, privatePem.pem, options);

        var valid = clientAuth
            .verify(token,
                    publicJwk,
                    options.issuer,
                    options.subject,
                    options.audience);

        expect(valid).to.eventually.be.ok;
    });

    it('should require the exp claim', function() {
        delete options.expiresInSeconds;
        var token = jwt.sign({}, privatePem.pem, options);

        var valid = clientAuth
            .verify(token,
                    publicJwk,
                    options.issuer,
                    options.subject,
                    options.audience);

        expect(valid).to.eventually.be.rejected;
    });

    it('should require consistent issuer', function() {
        var token = jwt.sign({}, privatePem.pem, options);

        var valid = clientAuth
            .verify(token,
                    publicJwk,
                    options.issuer + 'extra',
                    options.subject,
                    options.audience);

        expect(valid).to.eventually.be.rejected;
    });

    it('should require consistent subject', function() {
        var token = jwt.sign({}, privatePem.pem, options);

        var valid = clientAuth
            .verify(token,
                    publicJwk,
                    options.issuer,
                    options.subject + 'extra',
                    options.audience);

        expect(valid).to.eventually.be.rejected;
    });

    it('should require consistent audience', function() {
        var token = jwt.sign({}, privatePem.pem, options);

        var valid = clientAuth
            .verify(token,
                    publicJwk,
                    options.issuer,
                    options.subject,
                    options.audience + 'extra');

        expect(valid).to.eventually.be.rejected;
    });

    it('should enfore not before (nbf) claim', function() {
        var claims = {
            nbf: Math.floor((Date.now() + 10000) / 1000)
        };

        var token = jwt.sign(claims, privatePem.pem, options);

        var valid = clientAuth
            .verify(token,
                    publicJwk,
                    options.issuer,
                    options.subject,
                    options.audience);

        expect(valid).to.eventually.be.rejected;
    });

    it('should verify external claims', function() {
        var claims = {
            jti: '1234asdf'
        };

        var token = jwt.sign(claims, privatePem.pem, options);

        var valid = clientAuth
            .verify(token,
                    publicJwk,
                    options.issuer,
                    options.subject,
                    options.audience,
                    {payload: claims});

        expect(valid).to.eventually.be.ok;
    });

    it('should require consistent external claims', function() {
        var claims = {
            jti: '1234asdf'
        };

        var token = jwt.sign(claims, privatePem.pem, options);

        claims.jti += 'extra';
        var valid = clientAuth
            .verify(token,
                    publicJwk,
                    options.issuer,
                    options.subject,
                    options.audience,
                    {payload: claims});

        expect(valid).to.eventually.be.rejected;
    });

    it('should fail if key type not supported', function() {
        var token = jwt.sign({}, privatePem.pem, options);

        publicJwk.kty = '[Unknown]';
        var valid = clientAuth
            .verify(token,
                    publicJwk,
                    options.issuer,
                    options.subject,
                    options.audience + 'extra');

        expect(valid).to.eventually.be.rejected;
    });

    it('should fail if key type not supported', function() {
        var token = jwt.sign({}, privatePem.pem, options);

        publicJwk.kty = '[Unknown]';
        var valid = clientAuth
            .verify(token,
                    publicJwk,
                    options.issuer,
                    options.subject,
                    options.audience + 'extra');

        expect(valid).to.eventually.be.rejected;
    });

});
<commit_msg>Fixes test promises<commit_after>/* Copyright 2015 Open Ag Data Alliance
 *
 * 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.
 */

'use strict';

var chai = require('chai');
chai.use(require('chai-as-promised'));
var expect = chai.expect;

var fs = require('fs');
var jws = require('jws');
var jwt = require('jsonwebtoken');
var rsaPemToJwk = require('rsa-pem-to-jwk');
var clientAuth = require('../');

describe('exports', function() {
    ['generate', 'verify'].forEach(function(method) {
        it('should export ' + method, function() {
            expect(clientAuth[method]).to.be.a('function');
        });
    });
});

describe('generate', function() {
    var privatePem;
    var publicPem;
    var publicJwk;
    var expiresIn;
    var issuer;
    var clientId;
    var tokenEndpoint;

    function checkToken(token, cb) {
        var decoded = jws.decode(token);
        expect(decoded.header.kid).to.equal(privatePem.kid);
        expect(decoded.payload.exp - decoded.payload.iat).to.equal(expiresIn);
        expect(decoded.payload.iss).to.equal(issuer);
        expect(decoded.payload.sub).to.equal(clientId);
        expect(decoded.payload.aud).to.equal(tokenEndpoint);

        jwt.verify(token, publicPem.toString(), function(err) {
            expect(err).to.not.be.ok;

            cb(decoded);
        });
    }

    beforeEach(function() {
        privatePem = {
            kid: 'abc123',
            kty: 'PEM',
            pem: fs.readFileSync('test/keys/abc123.private.pem')
        };
        publicPem = fs.readFileSync('test/keys/abc123.public.pem');
        publicJwk = rsaPemToJwk(publicPem, {kid: 'abc123'});
        expiresIn = 123;
        issuer = 'fJd7s723qa';
        clientId = 'xi7sca3';
        tokenEndpoint = 'https://example.org/token';
    });

    it('should require object for key', function() {
        var token = clientAuth.generate();

        expect(token).to.equal(undefined);
    });

    it('should require a PEM key', function() {
        var token = clientAuth.generate({kty: 'RSA'});

        expect(token).to.equal(undefined);
    });

    it('should require issuer to be a string', function() {
        var token = clientAuth.generate(privatePem, expiresIn);

        expect(token).to.equal(undefined);
    });

    it('should require clientId to be a string', function() {
        var token = clientAuth.generate(privatePem, issuer, expiresIn);

        expect(token).to.equal(undefined);
    });

    it('should require tokenEndpoint to be a string', function() {
        var token = clientAuth.generate(privatePem, issuer, clientId,
            expiresIn);

        expect(token).to.equal(undefined);
    });

    it('should require expires to be a number', function() {
        var token = clientAuth.generate(privatePem, issuer, clientId,
            tokenEndpoint, 'expiresIn');

        expect(token).to.equal(undefined);
    });

    it('should require options to be an object', function() {
        var token = clientAuth.generate(privatePem, issuer, clientId,
            tokenEndpoint, expiresIn, 'options');

        expect(token).to.equal(undefined);
    });

    it('should allow no options', function(done) {
        var token = clientAuth.generate(privatePem, issuer, clientId,
            tokenEndpoint, expiresIn);

        checkToken(token, function() {
            done();
        });
    });

    it('should not allow overwriting required parameters', function(done) {
        var options = {
            algorithm: 'RS256' + 'invalid',
            issuer: issuer + 'invalid',
            subject: clientId + 'invalid',
            audience: tokenEndpoint + 'invalid',
            expiresInSeconds: expiresIn + 1000
        };

        var token = clientAuth.generate(privatePem, issuer, clientId,
            tokenEndpoint, expiresIn, options);

        checkToken(token, function() {
            done();
        });
    });

    it('should allow other payload claims', function(done) {
        var options = {
            payload: {
                jti: 'JTI'
            }
        };

        var token = clientAuth.generate(privatePem, issuer, clientId,
            tokenEndpoint, expiresIn, options);

        checkToken(token, function(decoded) {
            expect(decoded.payload.jti).to.equal('JTI');

            done();
        });
    });

    it('should allow a signing key with an id', function(done) {
        delete privatePem.kid;

        var token = clientAuth.generate(privatePem, issuer, clientId,
            tokenEndpoint, expiresIn);

        checkToken(token, function(decoded) {
            expect(decoded.header.kid).to.equal(undefined);

            done();
        });
    });
});

describe('verify', function() {
    var privatePem = {
        kid: 'abc123',
        kty: 'PEM',
        pem: fs.readFileSync('test/keys/abc123.private.pem')
    };
    var publicPem = fs.readFileSync('test/keys/abc123.public.pem');
    var publicJwk = rsaPemToJwk(publicPem, {kid: 'abc123'});
    var options;

    beforeEach(function() {
        options = {
            algorithm: 'RS256',
            issuer: 'Xjsi3f93',
            subject: 'vmaAU93F',
            audience: 'https://api.example.org/token',
            expiresInSeconds: 10,
            headers: {
                kid: privatePem.kid
            }
        };
    });

    it('should verify a valid token', function() {
        var token = jwt.sign({}, privatePem.pem, options);

        var valid = clientAuth
            .verify(token,
                    publicJwk,
                    options.issuer,
                    options.subject,
                    options.audience);

        return expect(valid).to.eventually.be.ok;
    });

    it('should require the exp claim', function() {
        delete options.expiresInSeconds;
        var token = jwt.sign({}, privatePem.pem, options);

        var valid = clientAuth
            .verify(token,
                    publicJwk,
                    options.issuer,
                    options.subject,
                    options.audience);

        return expect(valid).to.eventually.be.rejected;
    });

    it('should require consistent issuer', function() {
        var token = jwt.sign({}, privatePem.pem, options);

        var valid = clientAuth
            .verify(token,
                    publicJwk,
                    options.issuer + 'extra',
                    options.subject,
                    options.audience);

        return expect(valid).to.eventually.be.rejected;
    });

    it('should require consistent subject', function() {
        var token = jwt.sign({}, privatePem.pem, options);

        var valid = clientAuth
            .verify(token,
                    publicJwk,
                    options.issuer,
                    options.subject + 'extra',
                    options.audience);

        return expect(valid).to.eventually.be.rejected;
    });

    it('should require consistent audience', function() {
        var token = jwt.sign({}, privatePem.pem, options);

        var valid = clientAuth
            .verify(token,
                    publicJwk,
                    options.issuer,
                    options.subject,
                    options.audience + 'extra');

        return expect(valid).to.eventually.be.rejected;
    });

    it('should enfore not before (nbf) claim', function() {
        var claims = {
            nbf: Math.floor((Date.now() + 10000) / 1000)
        };

        var token = jwt.sign(claims, privatePem.pem, options);

        var valid = clientAuth
            .verify(token,
                    publicJwk,
                    options.issuer,
                    options.subject,
                    options.audience);

        return expect(valid).to.eventually.be.rejected;
    });

    it('should verify external claims', function() {
        var claims = {
            jti: '1234asdf'
        };

        var token = jwt.sign(claims, privatePem.pem, options);

        var valid = clientAuth
            .verify(token,
                    publicJwk,
                    options.issuer,
                    options.subject,
                    options.audience,
                    {payload: claims});

        return expect(valid).to.eventually.be.ok;
    });

    it('should require consistent external claims', function() {
        var claims = {
            jti: '1234asdf'
        };

        var token = jwt.sign(claims, privatePem.pem, options);

        claims.jti += 'extra';
        var valid = clientAuth
            .verify(token,
                    publicJwk,
                    options.issuer,
                    options.subject,
                    options.audience,
                    {payload: claims});

        return expect(valid).to.eventually.be.rejected;
    });

    it('should fail if key type not supported', function() {
        var token = jwt.sign({}, privatePem.pem, options);

        publicJwk.kty = '[Unknown]';
        var valid = clientAuth
            .verify(token,
                    publicJwk,
                    options.issuer,
                    options.subject,
                    options.audience + 'extra');

        return expect(valid).to.eventually.be.rejected;
    });

    it('should fail if key type not supported', function() {
        var token = jwt.sign({}, privatePem.pem, options);

        publicJwk.kty = '[Unknown]';
        var valid = clientAuth
            .verify(token,
                    publicJwk,
                    options.issuer,
                    options.subject,
                    options.audience + 'extra');

        return expect(valid).to.eventually.be.rejected;
    });

});
<|endoftext|>


<commit_before>(function(buster, define) {

var assert, fail, sentinel;

assert = buster.assert;
fail = buster.assertions.fail;

sentinel = { value: 'sentinel' };

define('when/monitor/PromiseMonitor-test', function (require) {

	var Promise = require('when/lib/Promise');
	var PromiseMonitor = require('when/monitor/PromiseMonitor');

	buster.testCase('when/monitor/PromiseMonitor', {

		tearDown: function() {
			console.promiseMonitor = void 0;
		},

		'reject should trigger report': function(done) {
			if (typeof console === 'undefined') {
				done();
				return;
			}

			console.promiseMonitor = new PromiseMonitor({ report: function () {
				assert(true);
				done();
			}});

			new Promise(function (_, reject) {
				reject();
			});
		},

		'Promise.reject should trigger report': function(done) {
			if (typeof console === 'undefined') {
				done();
				return;
			}

			console.promiseMonitor = new PromiseMonitor({ report: function () {
				assert(true);
				done();
			}});

			Promise.reject();
		}
	});

});
}(
	this.buster || require('buster'),
	typeof define === 'function' && define.amd ? define : function (id, factory) {
		var packageName = id.split(/[\/\-\.]/)[0], pathToRoot = id.replace(/[^\/]+/g, '..');
		pathToRoot = pathToRoot.length > 2 ? pathToRoot.substr(3) : pathToRoot;
		factory(function (moduleId) {
			return require(moduleId.indexOf(packageName) === 0 ? pathToRoot + moduleId.substr(packageName.length) : moduleId);
		});
	}
	// Boilerplate for AMD and Node
));
<commit_msg>Fix PromiseMonitor tearDown for envs with no console<commit_after>(function(buster, define) {

var assert, fail, sentinel;

assert = buster.assert;
fail = buster.assertions.fail;

sentinel = { value: 'sentinel' };

define('when/monitor/PromiseMonitor-test', function (require) {

	var Promise = require('when/lib/Promise');
	var PromiseMonitor = require('when/monitor/PromiseMonitor');

	buster.testCase('when/monitor/PromiseMonitor', {

		tearDown: function() {
			if (typeof console === 'undefined'
				&& typeof console.promiseMonitor !=='defined') {
				console.promiseMonitor = void 0;
			}
		},

		'reject should trigger report': function(done) {
			if (typeof console === 'undefined') {
				done();
				return;
			}

			console.promiseMonitor = new PromiseMonitor({ report: function () {
				assert(true);
				done();
			}});

			new Promise(function (_, reject) {
				reject();
			});
		},

		'Promise.reject should trigger report': function(done) {
			if (typeof console === 'undefined') {
				done();
				return;
			}

			console.promiseMonitor = new PromiseMonitor({ report: function () {
				assert(true);
				done();
			}});

			Promise.reject();
		}
	});

});
}(
	this.buster || require('buster'),
	typeof define === 'function' && define.amd ? define : function (id, factory) {
		var packageName = id.split(/[\/\-\.]/)[0], pathToRoot = id.replace(/[^\/]+/g, '..');
		pathToRoot = pathToRoot.length > 2 ? pathToRoot.substr(3) : pathToRoot;
		factory(function (moduleId) {
			return require(moduleId.indexOf(packageName) === 0 ? pathToRoot + moduleId.substr(packageName.length) : moduleId);
		});
	}
	// Boilerplate for AMD and Node
));
<|endoftext|>


<commit_before>var loadedImages = {};
var centerZoomBased;
var staticMapOptions;
var mapWidth;
var mapHeight;
var apiURL;

function hideMapTabChildren() {
    var mapTab = document.getElementById("mapTab");
    for (var i = 0; i < mapTab.childNodes.length; i++) {
        var node = mapTab.childNodes[i];
        if (node.className == "image") {
            mapTab.removeChild(node);
            break;
        }
    }
    for (var i = 0; i < mapTab.childNodes.length; i++) {
        var node = mapTab.childNodes[i];
        if (node.className == "scrollers") {
            mapTab.removeChild(node);
            break;
        }
    }
}

function loadImage(imageURL,imageID) {
    if (!loadedImages[imageID]) {
        // Loads an image from the given URL into the image with the specified ID
        var img = document.getElementById(imageID);
        if (img) {
            if (imageURL != "") {
                img.src = imageURL;
            } else {
                img.src = "/common/images/blank.png";
            }
        }
        loadedImages[imageID] = true;
    }
}

function loadMapImage(newSrc) {
    var mapImage = document.getElementById("staticmapimage");
    var oldSrc = mapImage.src;
    mapImage.src = newSrc;
    if (oldSrc != newSrc) {
        show("loadingimage");
    }
}

function pixelsFromString(aString) {
    if (aString.substring(aString.length - 2, aString.length) == "px") {
        return aString.substring(0, aString.length - 2);
    } else if (aString.substring(aString.length - 1, aString.length) == "%") {
        return aString.substring(0, aString.length - 1);
    }
    return aString;
}

function zoomInFromCenter() {
    staticMapOptions['zoom'] = parseInt(staticMapOptions['zoom']) + 1;
    updateMapImage();
}

function zoomOutFromCenter() {
    staticMapOptions['zoom'] = parseInt(staticMapOptions['zoom']) - 1;
    updateMapImage();
}

function zoomInFromBBox() {
    var bbox = staticMapOptions['bbox'];
    var dLat = (bbox['ymax'] - bbox['ymin']) / 4;
    var dLon = (bbox['xmax'] - bbox['xmin']) / 4;
    bbox['ymax'] = bbox['ymax'] - dLat;
    bbox['xmax'] = bbox['xmax'] - dLon;
    bbox['ymin'] = bbox['ymin'] + dLat;
    bbox['xmin'] = bbox['xmin'] + dLon;
    staticMapOptions['bbox'] = bbox;
    updateMapImage();
}

function zoomOutFromBBox() {
    var bbox = staticMapOptions['bbox'];
    var dLat = (bbox['ymax'] - bbox['ymin']) / 2;
    var dLon = (bbox['xmax'] - bbox['xmin']) / 2;
    bbox['ymax'] = bbox['ymax'] + dLat;
    bbox['xmax'] = bbox['xmax'] + dLon;
    bbox['ymin'] = bbox['ymin'] - dLat;
    bbox['xmin'] = bbox['xmin'] - dLon;
    staticMapOptions['bbox'] = bbox;
    updateMapImage();
}

function addStaticMapControls() {
    if (!staticMapOptions) {
        return;
    }

    var objMap = document.getElementById("staticmapimage");
    mapWidth = objMap.clientWidth;
    mapHeight = objMap.clientHeight;

    var zoomIn = document.getElementById("zoomin");
    var zoomOut = document.getElementById("zoomout");
    var recenter = document.getElementById("recenter");

    centerZoomBased = ("center" in staticMapOptions);

    if (centerZoomBased) {
        zoomIn.onclick = zoomInFromCenter;
        zoomOut.onclick = zoomOutFromCenter;

        var initCenterLat = staticMapOptions['center']['lat'];
        var initCenterLon = staticMapOptions['center']['lon'];
        var initZoom = staticMapOptions['zoom'];
        recenter.onclick = function() {
            staticMapOptions['center'] = {'lat': initCenterLat, 'lon': initCenterLon};
            staticMapOptions['zoom'] = initZoom;
            updateMapImage();
        }

    } else {
        zoomIn.onclick = zoomInFromBBox;
        zoomOut.onclick = zoomOutFromBBox;

        var initBBox = staticMapOptions['bbox'];
        recenter.onclick = function() {
            staticMapOptions['bbox'] = initBBox;
            updateMapImage();
        }
    }
}

// north and east are sign arguments, e.g.:
// northeast is (1, 1)
// northwest is (1, -1)
// south is (-1, 0)
function scrollMap(north, east) {

    if (centerZoomBased) {
        var zoom = staticMapOptions['zoom'];
        var lat = staticMapOptions['center']['lat'];
        var lon = staticMapOptions['center']['lon'];
        var degreesCovered = 360 / Math.pow(2, parseInt(zoom) + 1);

        lat = parseFloat(lat) + degreesCovered * north;
        lon = parseFloat(lon) + degreesCovered * east;

        // round to 4 decimal places (roughly 10 meters)
        staticMapOptions['center']['lat'] = Math.round(lat * 10000) / 10000;
        staticMapOptions['center']['lon'] = Math.round(lon * 10000) / 10000;

    } else {
        var bbox = staticMapOptions['bbox'];
        var dLat = (bbox['ymax'] - bbox['ymin']) / 2;
        var dLon = (bbox['xmax'] - bbox['xmin']) / 2;
        bbox['ymax'] = bbox['ymax'] + dLat * north;
        bbox['ymin'] = bbox['ymin'] + dLat * north;
        bbox['xmax'] = bbox['xmax'] + dLon * east;
        bbox['xmin'] = bbox['xmin'] + dLon * east;
        staticMapOptions['bbox'] = bbox;
    }
    updateMapImage();
}

function updateMapImage() {
    var httpRequest = new XMLHttpRequest();
    var baseURL = staticMapOptions['baseURL'];
    var mapClass = staticMapOptions['mapClass'];
    var objMap = document.getElementById("staticmapimage");
    var url = apiURL + "?baseURL=" + baseURL + "&mapClass=" + mapClass + "&width=" + mapWidth + "&height=" + mapHeight;
    if (centerZoomBased) {
        var lat = staticMapOptions['center']['lat'];
        var lon = staticMapOptions['center']['lon'];
        var zoom = staticMapOptions['zoom'];
        url = url + "&lat=" + lat + "&lon=" + lon + "&zoom=" + zoom;
    } else {
        var bbox = staticMapOptions['bbox'];
        var bboxStr = bbox['xmin'] + "," + bbox['ymin'] + "," + bbox['xmax'] + "," + bbox['ymax'];
        url = url + "&bbox=" + bboxStr;
    }

    if ("projection" in staticMapOptions) {
        url = url + "&projection=" + staticMapOptions['projection'];
    }
    // code snippet from http://en.wikipedia.org/wiki/JSON#Use_in_Ajax
    httpRequest.open("GET", url, true);
    httpRequest.onreadystatechange = function() {
        if (httpRequest.readyState == 4 && httpRequest.status == 200) {
            var obj;
            if(window.JSON) {
                obj = JSON.parse(httpRequest.responseText);
            } else {
                obj = eval('(' + httpRequest.responseText + ')');
            }
            var newSrc = obj['response'];
            loadMapImage(newSrc);
        }
    }
    httpRequest.send(null);
}

// assuming only one of updateMapDimensions or updateContainerDimensions
// gets used so they can reference the same id
var updateMapDimensionsTimeoutId;

// Prevent firebombing the browser with Ajax calls on browsers which fire lots
// of resize events
function updateMapDimensions() {
    window.clearTimeout(updateMapDimensionsTimeoutId);
    updateMapDimensionsTimeoutId = window.setTimeout(doUpdateMapDimensions, 200);
}

function doUpdateMapDimensions() {
    if (!centerZoomBased) {
        // if width and height proprotions changed, we need to update the bbox
        var oldHeight = mapHeight;
        var oldWidth = mapWidth;
    }

    if (window.innerHeight !== undefined) {
        mapHeight = window.innerHeight;
    } else {
        mapHeight = document.documentElement.clientHeight; // ie7
    }

    if (window.innerWidth !== undefined) {
        mapWidth = window.innerWidth;
    } else {
        mapWidth = document.documentElement.clientWidth; // ie7
    }

    if (!centerZoomBased) {
        // if width and height changed, we need to update the bbox
        if ((oldWidth && oldWidth != mapWidth) || (oldHeight && oldHeight != mapHeight)) {
            var bbox = staticMapOptions['bbox'];
            var bboxWidth = bbox['xmax'] - bbox['xmin'];
            var bboxHeight = bbox['ymax'] - bbox['ymin'];
            var newBBoxWidth = bboxWidth * mapWidth / oldWidth;
            var newBBoxHeight = bboxHeight * mapHeight / oldHeight;

            var dWidth = (newBBoxWidth - bboxWidth) / 2;
            var dHeight = (newBBoxHeight - bboxHeight) / 2;

            bbox['xmax'] += dWidth;
            bbox['xmin'] -= dWidth;
            bbox['ymax'] += dHeight;
            bbox['ymin'] -= dHeight;

            staticMapOptions['bbox'] = bbox;
        }
    }

    var objMap = document.getElementById("mapimage");
	var objContainer = document.getElementById("container");
	var objScrollers = document.getElementById("mapscrollers");
    if (objContainer && objMap.className == "fullmap") {
        objContainer.style.width = mapWidth+"px";
        objContainer.style.height = mapHeight+"px";
        objMap.style.width = mapWidth+"px";
        objMap.style.height = mapHeight+"px";
    }
    if (objScrollers) {
        switch (getOrientation()) {
            case 'portrait':
              objScrollers.style.height = (mapHeight-42)+"px";
              objScrollers.style.width = mapWidth+"px";
            break;

            case 'landscape':
              objScrollers.style.height = mapHeight+"px";
              objScrollers.style.width = (mapWidth-42)+"px";
            break;
        }
    }

    updateMapImage();
}

// resizing counterpart for dynamic maps
function updateContainerDimensions() {
    window.clearTimeout(updateMapDimensionsTimeoutId);
    updateMapDimensionsTimeoutId = window.setTimeout(doUpdateContainerDimensions, 250);
}

function doUpdateContainerDimensions() {
    var container = document.getElementById("container");
    if (container) {
        if (window.innerWidth !== undefined) {
            container.style.width = window.innerWidth + "px";
        } else {
            container.style.width = document.documentElement.clientWidth + "px"; // ie7
        }
        if (window.innerHeight !== undefined) {
            container.style.height = window.innerHeight + "px";
        } else {
            container.style.height = document.documentElement.clientHeight + "px"; // ie7
        }
        if (typeof resizeMapOnContainerResize == 'function') {
            resizeMapOnContainerResize();
        }
    }
}

/*
function disable(strID) {
// Visually dims and disables the anchor whose id is strID
	var objA = document.getElementById(strID);
	if(objA) {
		if(objA.className.indexOf("disabled") == -1) { // only disable if it's not already disabled!
			objA.className = objA.className + " disabled";
		}
	}
}

function enable(strID) {
// Visually undims and re-enables the anchor whose id is strID
	var objA = document.getElementById(strID);
	if(objA) {
		objA.className = objA.className.replace("disabled","");
	}
}

function cancelOptions(strFormID) {
// Should cancel map-option changes and hide the form; this is just a stub for future real function
	var objForm = document.getElementById(strFormID);
	if(objForm) { objForm.reset() }
	hide("options");
}
*/

<commit_msg>pass through paths, markers and polygons when loading static map images<commit_after>var loadedImages = {};
var centerZoomBased;
var staticMapOptions;
var mapWidth;
var mapHeight;
var apiURL;

function hideMapTabChildren() {
    var mapTab = document.getElementById("mapTab");
    for (var i = 0; i < mapTab.childNodes.length; i++) {
        var node = mapTab.childNodes[i];
        if (node.className == "image") {
            mapTab.removeChild(node);
            break;
        }
    }
    for (var i = 0; i < mapTab.childNodes.length; i++) {
        var node = mapTab.childNodes[i];
        if (node.className == "scrollers") {
            mapTab.removeChild(node);
            break;
        }
    }
}

function loadImage(imageURL,imageID) {
    if (!loadedImages[imageID]) {
        // Loads an image from the given URL into the image with the specified ID
        var img = document.getElementById(imageID);
        if (img) {
            if (imageURL != "") {
                img.src = imageURL;
            } else {
                img.src = "/common/images/blank.png";
            }
        }
        loadedImages[imageID] = true;
    }
}

function loadMapImage(newSrc) {
    var mapImage = document.getElementById("staticmapimage");
    var oldSrc = mapImage.src;
    mapImage.src = newSrc;
    if (oldSrc != newSrc) {
        show("loadingimage");
    }
}

function pixelsFromString(aString) {
    if (aString.substring(aString.length - 2, aString.length) == "px") {
        return aString.substring(0, aString.length - 2);
    } else if (aString.substring(aString.length - 1, aString.length) == "%") {
        return aString.substring(0, aString.length - 1);
    }
    return aString;
}

function zoomInFromCenter() {
    staticMapOptions['zoom'] = parseInt(staticMapOptions['zoom']) + 1;
    updateMapImage();
}

function zoomOutFromCenter() {
    staticMapOptions['zoom'] = parseInt(staticMapOptions['zoom']) - 1;
    updateMapImage();
}

function zoomInFromBBox() {
    var bbox = staticMapOptions['bbox'];
    var dLat = (bbox['ymax'] - bbox['ymin']) / 4;
    var dLon = (bbox['xmax'] - bbox['xmin']) / 4;
    bbox['ymax'] = bbox['ymax'] - dLat;
    bbox['xmax'] = bbox['xmax'] - dLon;
    bbox['ymin'] = bbox['ymin'] + dLat;
    bbox['xmin'] = bbox['xmin'] + dLon;
    staticMapOptions['bbox'] = bbox;
    updateMapImage();
}

function zoomOutFromBBox() {
    var bbox = staticMapOptions['bbox'];
    var dLat = (bbox['ymax'] - bbox['ymin']) / 2;
    var dLon = (bbox['xmax'] - bbox['xmin']) / 2;
    bbox['ymax'] = bbox['ymax'] + dLat;
    bbox['xmax'] = bbox['xmax'] + dLon;
    bbox['ymin'] = bbox['ymin'] - dLat;
    bbox['xmin'] = bbox['xmin'] - dLon;
    staticMapOptions['bbox'] = bbox;
    updateMapImage();
}

function addStaticMapControls() {
    if (!staticMapOptions) {
        return;
    }

    var objMap = document.getElementById("staticmapimage");
    mapWidth = objMap.clientWidth;
    mapHeight = objMap.clientHeight;

    var zoomIn = document.getElementById("zoomin");
    var zoomOut = document.getElementById("zoomout");
    var recenter = document.getElementById("recenter");

    centerZoomBased = ("center" in staticMapOptions);

    if (centerZoomBased) {
        zoomIn.onclick = zoomInFromCenter;
        zoomOut.onclick = zoomOutFromCenter;

        var initCenterLat = staticMapOptions['center']['lat'];
        var initCenterLon = staticMapOptions['center']['lon'];
        var initZoom = staticMapOptions['zoom'];
        recenter.onclick = function() {
            staticMapOptions['center'] = {'lat': initCenterLat, 'lon': initCenterLon};
            staticMapOptions['zoom'] = initZoom;
            updateMapImage();
        }

    } else {
        zoomIn.onclick = zoomInFromBBox;
        zoomOut.onclick = zoomOutFromBBox;

        var initBBox = staticMapOptions['bbox'];
        recenter.onclick = function() {
            staticMapOptions['bbox'] = initBBox;
            updateMapImage();
        }
    }
}

// north and east are sign arguments, e.g.:
// northeast is (1, 1)
// northwest is (1, -1)
// south is (-1, 0)
function scrollMap(north, east) {

    if (centerZoomBased) {
        var zoom = staticMapOptions['zoom'];
        var lat = staticMapOptions['center']['lat'];
        var lon = staticMapOptions['center']['lon'];
        var degreesCovered = 360 / Math.pow(2, parseInt(zoom) + 1);

        lat = parseFloat(lat) + degreesCovered * north;
        lon = parseFloat(lon) + degreesCovered * east;

        // round to 4 decimal places (roughly 10 meters)
        staticMapOptions['center']['lat'] = Math.round(lat * 10000) / 10000;
        staticMapOptions['center']['lon'] = Math.round(lon * 10000) / 10000;

    } else {
        var bbox = staticMapOptions['bbox'];
        var dLat = (bbox['ymax'] - bbox['ymin']) / 2;
        var dLon = (bbox['xmax'] - bbox['xmin']) / 2;
        bbox['ymax'] = bbox['ymax'] + dLat * north;
        bbox['ymin'] = bbox['ymin'] + dLat * north;
        bbox['xmax'] = bbox['xmax'] + dLon * east;
        bbox['xmin'] = bbox['xmin'] + dLon * east;
        staticMapOptions['bbox'] = bbox;
    }
    updateMapImage();
}

function updateMapImage() {
    var httpRequest = new XMLHttpRequest();
    var baseURL = staticMapOptions['baseURL'];
    var mapClass = staticMapOptions['mapClass'];
    var objMap = document.getElementById("staticmapimage");
    var url = apiURL + "?baseURL=" + baseURL + "&mapClass=" + mapClass + "&width=" + mapWidth + "&height=" + mapHeight;
    if (centerZoomBased) {
        var lat = staticMapOptions['center']['lat'];
        var lon = staticMapOptions['center']['lon'];
        var zoom = staticMapOptions['zoom'];
        url = url + "&lat=" + lat + "&lon=" + lon + "&zoom=" + zoom;
    } else {
        var bbox = staticMapOptions['bbox'];
        var bboxStr = bbox['xmin'] + "," + bbox['ymin'] + "," + bbox['xmax'] + "," + bbox['ymax'];
        url = url + "&bbox=" + bboxStr;
    }

    if ("projection" in staticMapOptions) {
        url = url + "&projection=" + staticMapOptions['projection'];
    }
    if ('markers' in staticMapOptions && staticMapOptions['markers']) {
        url += '&markers=' + staticMapOptions['markers'];
    }
    if ('polygons' in staticMapOptions) {
        for (arg in staticMapOptions['polygons']) {
            url += '&'+arg+'='+staticMapOptions['polygons'][arg];
        }
    }
    if ('paths' in staticMapOptions) {
        for (arg in staticMapOptions['paths']) {
            url += '&'+arg+'='+staticMapOptions['paths'][arg];
        }
    }
    // code snippet from http://en.wikipedia.org/wiki/JSON#Use_in_Ajax
    httpRequest.open("GET", url, true);
    httpRequest.onreadystatechange = function() {
        if (httpRequest.readyState == 4 && httpRequest.status == 200) {
            var obj;
            if(window.JSON) {
                obj = JSON.parse(httpRequest.responseText);
            } else {
                obj = eval('(' + httpRequest.responseText + ')');
            }
            var newSrc = obj['response'];
            loadMapImage(newSrc);
        }
    }
    httpRequest.send(null);
}

// assuming only one of updateMapDimensions or updateContainerDimensions
// gets used so they can reference the same id
var updateMapDimensionsTimeoutId;

// Prevent firebombing the browser with Ajax calls on browsers which fire lots
// of resize events
function updateMapDimensions() {
    window.clearTimeout(updateMapDimensionsTimeoutId);
    updateMapDimensionsTimeoutId = window.setTimeout(doUpdateMapDimensions, 200);
}

function doUpdateMapDimensions() {
    if (!centerZoomBased) {
        // if width and height proprotions changed, we need to update the bbox
        var oldHeight = mapHeight;
        var oldWidth = mapWidth;
    }

    if (window.innerHeight !== undefined) {
        mapHeight = window.innerHeight;
    } else {
        mapHeight = document.documentElement.clientHeight; // ie7
    }

    if (window.innerWidth !== undefined) {
        mapWidth = window.innerWidth;
    } else {
        mapWidth = document.documentElement.clientWidth; // ie7
    }

    if (!centerZoomBased) {
        // if width and height changed, we need to update the bbox
        if ((oldWidth && oldWidth != mapWidth) || (oldHeight && oldHeight != mapHeight)) {
            var bbox = staticMapOptions['bbox'];
            var bboxWidth = bbox['xmax'] - bbox['xmin'];
            var bboxHeight = bbox['ymax'] - bbox['ymin'];
            var newBBoxWidth = bboxWidth * mapWidth / oldWidth;
            var newBBoxHeight = bboxHeight * mapHeight / oldHeight;

            var dWidth = (newBBoxWidth - bboxWidth) / 2;
            var dHeight = (newBBoxHeight - bboxHeight) / 2;

            bbox['xmax'] += dWidth;
            bbox['xmin'] -= dWidth;
            bbox['ymax'] += dHeight;
            bbox['ymin'] -= dHeight;

            staticMapOptions['bbox'] = bbox;
        }
    }

    var objMap = document.getElementById("mapimage");
	var objContainer = document.getElementById("container");
	var objScrollers = document.getElementById("mapscrollers");
    if (objContainer && objMap.className == "fullmap") {
        objContainer.style.width = mapWidth+"px";
        objContainer.style.height = mapHeight+"px";
        objMap.style.width = mapWidth+"px";
        objMap.style.height = mapHeight+"px";
    }
    if (objScrollers) {
        switch (getOrientation()) {
            case 'portrait':
              objScrollers.style.height = (mapHeight-42)+"px";
              objScrollers.style.width = mapWidth+"px";
            break;

            case 'landscape':
              objScrollers.style.height = mapHeight+"px";
              objScrollers.style.width = (mapWidth-42)+"px";
            break;
        }
    }

    updateMapImage();
}

// resizing counterpart for dynamic maps
function updateContainerDimensions() {
    window.clearTimeout(updateMapDimensionsTimeoutId);
    updateMapDimensionsTimeoutId = window.setTimeout(doUpdateContainerDimensions, 250);
}

function doUpdateContainerDimensions() {
    var container = document.getElementById("container");
    if (container) {
        if (window.innerWidth !== undefined) {
            container.style.width = window.innerWidth + "px";
        } else {
            container.style.width = document.documentElement.clientWidth + "px"; // ie7
        }
        if (window.innerHeight !== undefined) {
            container.style.height = window.innerHeight + "px";
        } else {
            container.style.height = document.documentElement.clientHeight + "px"; // ie7
        }
        if (typeof resizeMapOnContainerResize == 'function') {
            resizeMapOnContainerResize();
        }
    }
}

/*
function disable(strID) {
// Visually dims and disables the anchor whose id is strID
	var objA = document.getElementById(strID);
	if(objA) {
		if(objA.className.indexOf("disabled") == -1) { // only disable if it's not already disabled!
			objA.className = objA.className + " disabled";
		}
	}
}

function enable(strID) {
// Visually undims and re-enables the anchor whose id is strID
	var objA = document.getElementById(strID);
	if(objA) {
		objA.className = objA.className.replace("disabled","");
	}
}

function cancelOptions(strFormID) {
// Should cancel map-option changes and hide the form; this is just a stub for future real function
	var objForm = document.getElementById(strFormID);
	if(objForm) { objForm.reset() }
	hide("options");
}
*/

<|endoftext|>


<commit_before>
frappe.ready(() => {
    initialise_select_date()
})
window.holiday_list = [];

async function initialise_select_date() {
    navigate_to_page(1);
    await get_global_variables();
    setup_date_picker();
    setup_timezone_selector();
    hide_next_button();
}

async function get_global_variables() {
    window.appointment_settings = (await frappe.call({
        method: 'erpnext.www.book-appointment.index.get_appointment_settings'
    })).message
    window.timezones = (await frappe.call({
        method: 'erpnext.www.book-appointment.index.get_timezones'
    })).message;
    window.holiday_list = (await frappe.call({
        method: 'erpnext.www.book-appointment.index.get_holiday_list',
        args: {
            'holiday_list_name': window.appointment_settings.holiday_list
        }
    })).message;
}

function setup_timezone_selector() {
    let timezones_element = document.getElementById('appointment-timezone');
    var offset = new Date().getTimezoneOffset();
    window.timezones.forEach(timezone => {
        var opt = document.createElement('option');
        opt.value = timezone.offset;
        opt.innerHTML = timezone.timezone_name;
        opt.defaultSelected = (offset == timezone.offset)
        timezones_element.appendChild(opt)
    });
}

function setup_date_picker() {
    let date_picker = document.getElementById('appointment-date');
    let today = new Date();
    date_picker.min = today.toISOString().substr(0, 10);
    today.setDate(today.getDate() + window.appointment_settings.advance_booking_days);
    date_picker.max = today.toISOString().substr(0,10);
}

function hide_next_button(){
    let next_button = document.getElementById('next-button');
    next_button.disabled = true;
    next_button.onclick = ()=>{frappe.msgprint("Please select a date and time")};
}

function show_next_button(){
    let next_button = document.getElementById('next-button');
    next_button.disabled = false;
    next_button.onclick = setup_details_page;
}

function on_date_or_timezone_select() {
    let date_picker = document.getElementById('appointment-date');
    let timezone = document.getElementById('appointment-timezone');
    if (date_picker.value === '') {
        clear_time_slots();
        hide_next_button();
        frappe.throw('Please select a date');
    }
    window.selected_date = date_picker.value;
    window.selected_timezone = timezone.value;
    update_time_slots(date_picker.value, timezone.value);
    let lead_text = document.getElementById('lead-text');
    lead_text.innerHTML = "Select Time"
}

async function get_time_slots(date, timezone) {
    debugger
    let slots = (await frappe.call({
        method: 'erpnext.www.book-appointment.index.get_appointment_slots',
        args: {
            date: date,
            timezone: timezone
        }
    })).message;
    return slots;
}

async function update_time_slots(selected_date, selected_timezone) {
    let timeslot_container = document.getElementById('timeslot-container');
    window.slots = await get_time_slots(selected_date, selected_timezone);
    clear_time_slots();
    if (window.slots.length <= 0) {
        let message_div = document.createElement('p');
        message_div.innerHTML = "There are no slots available on this date";
        timeslot_container.appendChild(message_div);
        return
    }
    window.slots.forEach((slot,index) => {
        debugger
        if(index%8==0){
            let break_element = document.createElement('div');
            break_element.classList.add('w-100');
            timeslot_container.appendChild(break_element);
        }
        let start_time = new Date(slot.time)
        var timeslot_div = document.createElement('div');
        timeslot_div.classList.add('time-slot');
        timeslot_div.classList.add('col-md');
        if (!slot.availability) {
            timeslot_div.classList.add('unavailable')
        }
        timeslot_div.innerHTML = get_slot_layout(start_time);
        timeslot_div.id = slot.time.substr(11, 20);
        timeslot_div.addEventListener('click', select_time);
        timeslot_container.appendChild(timeslot_div);
    });
    set_default_timeslot();
}

function clear_time_slots() {
    let timeslot_container = document.getElementById('timeslot-container');
    while (timeslot_container.firstChild) {
        timeslot_container.removeChild(timeslot_container.firstChild)
    }
}

function get_slot_layout(time) {
    time = new Date(time)
    let start_time_string = moment(time).format("LT");
    let end_time = moment(time).add(window.appointment_settings.appointment_duration,'minutes');
    let end_time_string = end_time.format("LT");
    return `<span style="font-size: 1.2em;">${start_time_string}</span><br><span class="text-muted small">to ${end_time_string}</span>`;
}

function select_time() {
    if (this.classList.contains("unavailable")) {
        return
    }
    try {
        selected_element = document.getElementsByClassName('selected')[0]
    } catch (e) {
        debugger
        this.classList.add("selected")
    }
    window.selected_time = this.id
    selected_element.classList.remove("selected");
    this.classList.add("selected");
    show_next_button();
}

function set_default_timeslot() {
    let timeslots = document.getElementsByClassName('time-slot')
    for (let i = 0; i < timeslots.length; i++) {
        const timeslot = timeslots[i];
        if (!timeslot.classList.contains('unavailable')) {
            timeslot.classList.add("selected");
            break;
        }
    }
}

function navigate_to_page(page_number){
    let page1 = document.getElementById('select-date-time');
    let page2 = document.getElementById('enter-details');
    switch(page_number){
        case 1:
            page1.style.display = 'block';
            page2.style.display = 'none';
            break;
        case 2:
            page1.style.display = 'none';
            page2.style.display = 'block';
            break;
        default:
            console.log("That's not a valid page")
    }
}

function setup_details_page(){
    navigate_to_page(2)
    let date_container = document.getElementsByClassName('date-span')[0];
    let time_container = document.getElementsByClassName('time-span')[0];
    date_container.innerHTML = moment(window.selected_date).format("MMM Do YYYY");
    time_container.innerHTML = moment(window.selected_time,"HH:mm:ss").format("LT");
}

async function submit() {
    // form validation here
    form_validation();
    debugger;
    let appointment = (await frappe.call({
        method: 'erpnext.www.book-appointment.index.create_appointment',
        args: {
            'date': window.selected_date,
            'time': window.selected_time,
            'contact': window.contact
        }
    })).message;
    frappe.msgprint(__('Appointment Created Successfully'));
    let button = document.getElementById('submit-button');
    button.disabled = true;
    button.onclick = () => { console.log('This should never have happened') }
}

function form_validation(){
    var date = window.selected_date;
    var time = window.selected_time;
    contact = {};
    contact.name = document.getElementById('customer_name').value;
    contact.number = document.getElementById('customer_number').value;
    contact.skype = document.getElementById('customer_skype').value;
    contact.notes = document.getElementById('customer_notes').value;
    contact.email = document.getElementById('customer_email').value;
    window.contact = contact
    console.log({ date, time, contact });
}
<commit_msg>removed debugger<commit_after>
frappe.ready(() => {
    initialise_select_date()
})
window.holiday_list = [];

async function initialise_select_date() {
    navigate_to_page(1);
    await get_global_variables();
    setup_date_picker();
    setup_timezone_selector();
    hide_next_button();
}

async function get_global_variables() {
    window.appointment_settings = (await frappe.call({
        method: 'erpnext.www.book-appointment.index.get_appointment_settings'
    })).message
    window.timezones = (await frappe.call({
        method: 'erpnext.www.book-appointment.index.get_timezones'
    })).message;
    window.holiday_list = (await frappe.call({
        method: 'erpnext.www.book-appointment.index.get_holiday_list',
        args: {
            'holiday_list_name': window.appointment_settings.holiday_list
        }
    })).message;
}

function setup_timezone_selector() {
    let timezones_element = document.getElementById('appointment-timezone');
    var offset = new Date().getTimezoneOffset();
    window.timezones.forEach(timezone => {
        var opt = document.createElement('option');
        opt.value = timezone.offset;
        opt.innerHTML = timezone.timezone_name;
        opt.defaultSelected = (offset == timezone.offset)
        timezones_element.appendChild(opt)
    });
}

function setup_date_picker() {
    let date_picker = document.getElementById('appointment-date');
    let today = new Date();
    date_picker.min = today.toISOString().substr(0, 10);
    today.setDate(today.getDate() + window.appointment_settings.advance_booking_days);
    date_picker.max = today.toISOString().substr(0,10);
}

function hide_next_button(){
    let next_button = document.getElementById('next-button');
    next_button.disabled = true;
    next_button.onclick = ()=>{frappe.msgprint("Please select a date and time")};
}

function show_next_button(){
    let next_button = document.getElementById('next-button');
    next_button.disabled = false;
    next_button.onclick = setup_details_page;
}

function on_date_or_timezone_select() {
    let date_picker = document.getElementById('appointment-date');
    let timezone = document.getElementById('appointment-timezone');
    if (date_picker.value === '') {
        clear_time_slots();
        hide_next_button();
        frappe.throw('Please select a date');
    }
    window.selected_date = date_picker.value;
    window.selected_timezone = timezone.value;
    update_time_slots(date_picker.value, timezone.value);
    let lead_text = document.getElementById('lead-text');
    lead_text.innerHTML = "Select Time"
}

async function get_time_slots(date, timezone) {
    let slots = (await frappe.call({
        method: 'erpnext.www.book-appointment.index.get_appointment_slots',
        args: {
            date: date,
            timezone: timezone
        }
    })).message;
    return slots;
}

async function update_time_slots(selected_date, selected_timezone) {
    let timeslot_container = document.getElementById('timeslot-container');
    window.slots = await get_time_slots(selected_date, selected_timezone);
    clear_time_slots();
    if (window.slots.length <= 0) {
        let message_div = document.createElement('p');
        message_div.innerHTML = "There are no slots available on this date";
        timeslot_container.appendChild(message_div);
        return
    }
    window.slots.forEach((slot,index) => {
        if(index%8==0){
            let break_element = document.createElement('div');
            break_element.classList.add('w-100');
            timeslot_container.appendChild(break_element);
        }
        let start_time = new Date(slot.time)
        var timeslot_div = document.createElement('div');
        timeslot_div.classList.add('time-slot');
        timeslot_div.classList.add('col-md');
        if (!slot.availability) {
            timeslot_div.classList.add('unavailable')
        }
        timeslot_div.innerHTML = get_slot_layout(start_time);
        timeslot_div.id = slot.time.substr(11, 20);
        timeslot_div.addEventListener('click', select_time);
        timeslot_container.appendChild(timeslot_div);
    });
    set_default_timeslot();
}

function clear_time_slots() {
    let timeslot_container = document.getElementById('timeslot-container');
    while (timeslot_container.firstChild) {
        timeslot_container.removeChild(timeslot_container.firstChild)
    }
}

function get_slot_layout(time) {
    time = new Date(time)
    let start_time_string = moment(time).format("LT");
    let end_time = moment(time).add(window.appointment_settings.appointment_duration,'minutes');
    let end_time_string = end_time.format("LT");
    return `<span style="font-size: 1.2em;">${start_time_string}</span><br><span class="text-muted small">to ${end_time_string}</span>`;
}

function select_time() {
    if (this.classList.contains("unavailable")) {
        return
    }
    try {
        selected_element = document.getElementsByClassName('selected')[0]
    } catch (e) {
        this.classList.add("selected")
    }
    window.selected_time = this.id
    selected_element.classList.remove("selected");
    this.classList.add("selected");
    show_next_button();
}

function set_default_timeslot() {
    let timeslots = document.getElementsByClassName('time-slot')
    for (let i = 0; i < timeslots.length; i++) {
        const timeslot = timeslots[i];
        if (!timeslot.classList.contains('unavailable')) {
            timeslot.classList.add("selected");
            break;
        }
    }
}

function navigate_to_page(page_number){
    let page1 = document.getElementById('select-date-time');
    let page2 = document.getElementById('enter-details');
    switch(page_number){
        case 1:
            page1.style.display = 'block';
            page2.style.display = 'none';
            break;
        case 2:
            page1.style.display = 'none';
            page2.style.display = 'block';
            break;
        default:
            console.log("That's not a valid page")
    }
}

function setup_details_page(){
    navigate_to_page(2)
    let date_container = document.getElementsByClassName('date-span')[0];
    let time_container = document.getElementsByClassName('time-span')[0];
    date_container.innerHTML = moment(window.selected_date).format("MMM Do YYYY");
    time_container.innerHTML = moment(window.selected_time,"HH:mm:ss").format("LT");
}

async function submit() {
    // form validation here
    form_validation();
    let appointment = (await frappe.call({
        method: 'erpnext.www.book-appointment.index.create_appointment',
        args: {
            'date': window.selected_date,
            'time': window.selected_time,
            'contact': window.contact
        }
    })).message;
    frappe.msgprint(__('Appointment Created Successfully'));
    let button = document.getElementById('submit-button');
    button.disabled = true;
    button.onclick = () => { console.log('This should never have happened') }
}

function form_validation(){
    var date = window.selected_date;
    var time = window.selected_time;
    contact = {};
    contact.name = document.getElementById('customer_name').value;
    contact.number = document.getElementById('customer_number').value;
    contact.skype = document.getElementById('customer_skype').value;
    contact.notes = document.getElementById('customer_notes').value;
    contact.email = document.getElementById('customer_email').value;
    window.contact = contact
    console.log({ date, time, contact });
}
<|endoftext|>