<commit_before>/* Copyright G. Hemingway, 2015 - All rights reserved */
"use strict";

var path            = require('path'),
    fs              = require('fs'),
    _               = require('lodash');

/**********************************************************************************/

/*
 * Establish the configuration of the application
 *
 * @param {configFile} String of the path for the configuration file to be loaded
 * @param {environment} Which profile in the configuration file should be loaded
 * @return {config} The configuration for the given profile
 */
module.exports = function(environment) {
    var config = {
          "host": "",
          "port": 8080,
          "protocol": "http",
          "api_version": "v3",
          "client": {
            "embedded": false,
            "auth": false,
            "two_factor": true,
            "socket": true,
            "upload": true
          },
          "file" : {
            "dir": "./boxy.stpnc"
          },
          "machine" : {
            "dir" : ""
          },
          "auth": {
            "module": "permit"
          },
          "environments": {
            "development": {
              "storage": {
                "module": "filesystem",
                "options": {
                  "dir": "./data"
                }
              }
            }
        }
    };
    try {
        config.env = environment;
        // Blend in the environment
        var env = config.environments[environment];
        delete config.environments;
        if (env) {
            _.each(_.keys(config),function(key) {
                if (typeof env[key] !== 'undefined') {
                    if (typeof config[key] === 'object') {
                        _.extend(config[key], env[key]);
                    } else {
                        config[key] = env[key];
                    }
                }
            });
        }
        // Add root directory into the mix
        config.rootDir = path.join(__dirname, '../../');
    } catch (err) {
        console.log('Error in node-configurator: %s', err);
        process.exit(-1);
        return config;
    }
    return config;
};<commit_msg>took more useless stuff out<commit_after>/* Copyright G. Hemingway, 2015 - All rights reserved */
"use strict";

var path            = require('path'),
    fs              = require('fs'),
    _               = require('lodash');

/**********************************************************************************/

/*
 * Establish the configuration of the application
 *
 * @param {configFile} String of the path for the configuration file to be loaded
 * @param {environment} Which profile in the configuration file should be loaded
 * @return {config} The configuration for the given profile
 */
module.exports = function(environment) {
    var config = {
          "host": "",
          "port": 8080,
          "protocol": "http",
          "api_version": "v3",
          "client": {
            "embedded": false,
            "auth": false,
            "two_factor": true,
            "socket": true,
            "upload": true
          },
          "file" : {
            "dir": "./boxy.stpnc"
          },
          "machine" : {
            "dir" : ""
          },
          "environments": {
            "development": {
              "storage": {
                "module": "filesystem",
                "options": {
                  "dir": "./data"
                }
              }
            }
        }
    };
    try {
        config.env = environment;
        // Blend in the environment
        var env = config.environments[environment];
        delete config.environments;
        if (env) {
            _.each(_.keys(config),function(key) {
                if (typeof env[key] !== 'undefined') {
                    if (typeof config[key] === 'object') {
                        _.extend(config[key], env[key]);
                    } else {
                        config[key] = env[key];
                    }
                }
            });
        }
        // Add root directory into the mix
        config.rootDir = path.join(__dirname, '../../');
    } catch (err) {
        console.log('Error in node-configurator: %s', err);
        process.exit(-1);
        return config;
    }
    return config;
};<|endoftext|>


<commit_before>var mongoose = require('mongoose');
var PhoneGroup = require('./phone-group').PhoneGroup;
var textMessage = require('../text_message.js');

var outgoingMessageSchema = new mongoose.Schema({
  body: { type: String, required: true },
  uuid: { type: String },
  messageStatus: { type: String },
  createdAt: { type: Date },
  updatedAt: { type: Date }
});

outgoingMessageSchema.pre('save', function(next){
  now = new Date();
  this.updatedAt = now;
  if ( !this.createdAt ) {
    this.createdAt = now;
  }
  next();
});

var incomingMessageSchema = new mongoose.Schema({
  body: { type: String, required: true },
  raw: { type: String },
  createdAt: { type: Date },
  updatedAt: { type: Date }
});

incomingMessageSchema.pre('save', function(next){
  now = new Date();
  this.updatedAt = now;
  if ( !this.createdAt ) {
    this.createdAt = now;
  }
  next();
});

var phoneSchema = new mongoose.Schema({
  number: { type: Number, unique: true },
  active: { type: Boolean, required: true, default: true },
  incomingMessages: [{ type: mongoose.Schema.Types.ObjectId, ref: 'IncomingMessage' }],
  // incomingMessages: [{
  //   raw: { type: String },
  //   body: { type: String }
  // }],
  outgoingMessages: [{ type: mongoose.Schema.Types.ObjectId, ref: 'OutgoingMessage' }],
  createdAt: { type: Date },
  updatedAt: { type: Date }
});

phoneSchema.pre('save', function(next){
  now = new Date();
  this.updatedAt = now;
  if ( !this.createdAt ) {
    this.createdAt = now;
  }
  next();
});

phoneSchema.methods.addToGroup = function(group) {
  group.phones.addToSet(this._id)
  return group.saveAsync();
}

phoneSchema.methods.sendMessage = function(message) {
  var self = this;
  if (!self.active) {
    // do nothing now...
    //throw new Error('can not send to inactive phone');
  }
  else {
    var om = new OutgoingMessage({body: message})
    return om.save().then(function() {
      self.outgoingMessages.push(om);
      return self.saveAsync();
    }).then(function() {
      return textMessage.send(self.number, message);
    }).then(function(res) {
      // TODO: move to callback?...
      if(/queued/.test(res[0].body.message)) {
        om.messageStatus = 'queued';
        om.uuid = res[0].body.message_uuid;
        return om.saveAsync();
      }
      else {
        console.log(res[0].body);
        // do nothing now...
        //throw new Error('something went wrong with text message creation');
      }
    });
  }
};

phoneSchema.methods.processStopKeywords = function(kw) {
  var deactivate = false;
  var stopKeywords = ['stop', 'end', 'unsubscribe', 'remove', 'quit', 'block']
  stopKeywords.forEach(function(skw) {
    if (kw.toLowerCase() == skw.toLowerCase()) {
      this.sendMessage(process.env.UNSUB_MESSAGE);
      deactivate = true;
    }
  }.bind(this));
  this.active = deactivate ? false : true;
};

phoneSchema.methods.processHelpKeyword = function() {
  //var activeState = this.active;
  this.sendMessage(process.env.HELP_MESSAGE);
}

var Phone = mongoose.model('Phone', phoneSchema);
var OutgoingMessage = mongoose.model('OutgoingMessage', outgoingMessageSchema);
var IncomingMessage = mongoose.model('IncomingMessage', incomingMessageSchema);

Phone.handleIncomingMessage = function(values) {
  //if ( !values['From'] ) { throw 'from value is undef.' }
  console.log (values);
  var im = new IncomingMessage({ raw: JSON.stringify(values), body: values.Text });
  var firstWord = im.body.trim().split(' ')[0];
  var phoneId;
  return Promise.resolve(Phone.findOne({number: values.From}).execAsync()
    .then(function(p) {
      p = p ? p : new Phone({number: values.From})
      p.incomingMessages.push(im);
      p.processStopKeywords(firstWord);
      if(firstWord == 'help') {
        p.processHelpKeyword();
      }
      phoneId = p._id;
      return p.saveAsync();
    }).then(function(p1) {
      if(firstWord != 'help') {
        return p1[0].active ? PhoneGroup.findKeywordAndAddToGroup(firstWord, p1[0]) : null;
      }
      // return PhoneGroup.findKeywordAndAddToGroup(firstWord, p1[0]);
    }).then(function(){
      return Phone.findById(phoneId).execAsync();
    }).then(function(rp) {
      return rp;
    })
  );
};

module.exports = {
  Phone: Phone,
  OutgoingMessage: OutgoingMessage,
  IncomingMessage: IncomingMessage
};
<commit_msg>debugging<commit_after>var mongoose = require('mongoose');
var PhoneGroup = require('./phone-group').PhoneGroup;
var textMessage = require('../text_message.js');

var outgoingMessageSchema = new mongoose.Schema({
  body: { type: String, required: true },
  uuid: { type: String },
  messageStatus: { type: String },
  createdAt: { type: Date },
  updatedAt: { type: Date }
});

outgoingMessageSchema.pre('save', function(next){
  now = new Date();
  this.updatedAt = now;
  if ( !this.createdAt ) {
    this.createdAt = now;
  }
  next();
});

var incomingMessageSchema = new mongoose.Schema({
  body: { type: String, required: true },
  raw: { type: String },
  createdAt: { type: Date },
  updatedAt: { type: Date }
});

incomingMessageSchema.pre('save', function(next){
  now = new Date();
  this.updatedAt = now;
  if ( !this.createdAt ) {
    this.createdAt = now;
  }
  next();
});

var phoneSchema = new mongoose.Schema({
  number: { type: Number, unique: true },
  active: { type: Boolean, required: true, default: true },
  incomingMessages: [{ type: mongoose.Schema.Types.ObjectId, ref: 'IncomingMessage' }],
  // incomingMessages: [{
  //   raw: { type: String },
  //   body: { type: String }
  // }],
  outgoingMessages: [{ type: mongoose.Schema.Types.ObjectId, ref: 'OutgoingMessage' }],
  createdAt: { type: Date },
  updatedAt: { type: Date }
});

phoneSchema.pre('save', function(next){
  now = new Date();
  this.updatedAt = now;
  if ( !this.createdAt ) {
    this.createdAt = now;
  }
  next();
});

phoneSchema.methods.addToGroup = function(group) {
  group.phones.addToSet(this._id)
  return group.saveAsync();
}

phoneSchema.methods.sendMessage = function(message) {
  var self = this;
  if (!self.active) {
    // do nothing now...
    //throw new Error('can not send to inactive phone');
  }
  else {
    var om = new OutgoingMessage({body: message})
    return om.save().then(function() {
      self.outgoingMessages.push(om);
      return self.saveAsync();
    }).then(function() {
      return textMessage.send(self.number, message);
    }).then(function(res) {
      // TODO: move to callback?...
      if(/queued/.test(res[0].body.message)) {
        om.messageStatus = 'queued';
        om.uuid = res[0].body.message_uuid;
        return om.saveAsync();
      }
      else {
        console.log(res[0].body);
        // do nothing now...
        //throw new Error('something went wrong with text message creation');
      }
    });
  }
};

phoneSchema.methods.processStopKeywords = function(kw) {
  var deactivate = false;
  var stopKeywords = ['stop', 'end', 'unsubscribe', 'remove', 'quit', 'block']
  stopKeywords.forEach(function(skw) {
    if (kw.toLowerCase() == skw.toLowerCase()) {
      this.sendMessage(process.env.UNSUB_MESSAGE);
      deactivate = true;
    }
  }.bind(this));
  this.active = deactivate ? false : true;
};

phoneSchema.methods.processHelpKeyword = function() {
  //var activeState = this.active;
  this.sendMessage(process.env.HELP_MESSAGE);
}

var Phone = mongoose.model('Phone', phoneSchema);
var OutgoingMessage = mongoose.model('OutgoingMessage', outgoingMessageSchema);
var IncomingMessage = mongoose.model('IncomingMessage', incomingMessageSchema);

Phone.handleIncomingMessage = function(values) {
  //if ( !values['From'] ) { throw 'from value is undef.' }
  console.log (values);
  var im = new IncomingMessage({ raw: JSON.stringify(values), body: values.Text });
  console.log (im);
  var firstWord = im.body.trim().split(' ')[0];
  var phoneId;
  return Promise.resolve(Phone.findOne({number: values.From}).execAsync()
    .then(function(p) {
      p = p ? p : new Phone({number: values.From})
      p.incomingMessages.push(im);
      p.processStopKeywords(firstWord);
      if(firstWord == 'help') {
        p.processHelpKeyword();
      }
      phoneId = p._id;
      return p.saveAsync();
    }).then(function(p1) {
      if(firstWord != 'help') {
        return p1[0].active ? PhoneGroup.findKeywordAndAddToGroup(firstWord, p1[0]) : null;
      }
      // return PhoneGroup.findKeywordAndAddToGroup(firstWord, p1[0]);
    }).then(function(){
      return Phone.findById(phoneId).execAsync();
    }).then(function(rp) {
      return rp;
    })
  );
};

module.exports = {
  Phone: Phone,
  OutgoingMessage: OutgoingMessage,
  IncomingMessage: IncomingMessage
};
<|endoftext|>


<commit_before>var debug = require('debug')('wol:leaderboard'),
    $db = require('../lib/mongo'),
    gameres = require('../lib/gameres');

exports.buffer = function(req, res, next) {
    $db.get(req.params.game +'_dumps').findOne({idno: parseInt(req.params.gameId)}, function(err, data) {

        if (!data.buffers || data.buffers.length < 1) return res.send(404);

        data.buffers.forEach(function(buffer, index) {
            data.buffers[index] = buffer.buffer.toString('hex');
        });

        res.send(data.buffers);
    });
};

exports.gameres = function(req, res, next) {
    $db.get(req.params.game +'_dumps').findOne({idno: parseInt(req.params.gameId)}, function(err, data) {

        if (!data.buffers || data.buffers.length < 1) return res.send(404);

        data.buffers.forEach(function(buffer, index) {
            data.buffers[index] = gameres.parse(buffer.buffer);
            delete data.buffers[index].buffer;
        });

        res.send(data.buffers);
    });
};

exports.submit = function(req, res, next) {

    if (!req.body) return res.send(400);

    res.send(gameres.parse(req.body));
};
<commit_msg>ensure data is an obj<commit_after>var debug = require('debug')('wol:leaderboard'),
    $db = require('../lib/mongo'),
    gameres = require('../lib/gameres');

exports.buffer = function(req, res, next) {
    $db.get(req.params.game +'_dumps').findOne({idno: parseInt(req.params.gameId)}, function(err, data) {
        data = data || {};
        if (!data.buffers || data.buffers.length < 1) return res.send(404);

        data.buffers.forEach(function(buffer, index) {
            data.buffers[index] = buffer.buffer.toString('hex');
        });

        res.send(data.buffers);
    });
};

exports.gameres = function(req, res, next) {
    $db.get(req.params.game +'_dumps').findOne({idno: parseInt(req.params.gameId)}, function(err, data) {
        data = data || {};
        if (!data.buffers || data.buffers.length < 1) return res.send(404);

        data.buffers.forEach(function(buffer, index) {
            data.buffers[index] = gameres.parse(buffer.buffer);
            delete data.buffers[index].buffer;
        });

        res.send(data.buffers);
    });
};

exports.submit = function(req, res, next) {

    if (!req.body) return res.send(400);

    res.send(gameres.parse(req.body));
};
<|endoftext|>


<commit_before>import fs from 'fs'
import path from 'path'
import { spawn } from 'child_process'
import crypto from 'crypto'
import router from 'koa-router'

export default router()
.post('/api/hooks', async(ctx, next) => {
    await new Promise((resolve) => {
        const { fields, headers } = ctx.request
        const signature = getSecret(JSON.stringify(fields))

        if (signature === headers['x-hub-signature'].substr(5)) {
            console.info('Signature matched, restarting server...')
            pullAndUpdate().then(() => {
                ctx.body = {
                    success: true
                }
                resolve()
            })
        } else {
            console.warn('Signature mismatch')
            ctx.body = {
                success: false
            }
            resolve()
        }
    })
})

// Checks if we're authorized to restart the server
function getSecret(body) {
    const INFERNOJS_SECRET = fs.readFileSync(path.join(__dirname, 'INFERNOJS_SECRET'))
    const secret = process.env.INFERNOJS_SECRET || INFERNOJS_SECRET
    const hash = crypto.createHmac('sha1', secret).update(body).digest('hex')
    return hash
}

// Pulls master from github while our watcher automatically rebuilds the bundle
function pullAndUpdate() {
    return execute('cd', ['/www/inferno-website'])
    .then(() => execute('git', ['pull']))
}

// Run commands
function execute(cmd, args) {
    return new Promise((resolve) => {
        console.log('Command:', cmd, args.join(' '))

        const child = spawn(cmd, args);
        let output = '';

        child.stdout.on('data', function (buffer) { output += buffer.toString() });
        child.stdout.on('end', function() {
            console.log('Output:', output)
            resolve(output)
        });
    })
}
<commit_msg>Fix deploy hook 5 (restart server)<commit_after>import fs from 'fs'
import path from 'path'
import { spawn } from 'child_process'
import crypto from 'crypto'
import router from 'koa-router'

export default router()
.post('/api/hooks', async(ctx, next) => {
    await new Promise((resolve) => {
        const { fields, headers } = ctx.request
        const signature = getSecret(JSON.stringify(fields))

        if (signature === headers['x-hub-signature'].substr(5)) {
            console.info('Signature matched, restarting server...')
            pullAndUpdate().then(() => {
                ctx.body = {
                    success: true
                }
                resolve()
            })
        } else {
            console.warn('Signature mismatch')
            ctx.body = {
                success: false
            }
            resolve()
        }
    })
})

// Checks if we're authorized to restart the server
function getSecret(body) {
    const INFERNOJS_SECRET = fs.readFileSync(path.join(__dirname, 'INFERNOJS_SECRET'))
    const secret = process.env.INFERNOJS_SECRET || INFERNOJS_SECRET
    const hash = crypto.createHmac('sha1', secret).update(body).digest('hex')
    return hash
}

// Pulls master from github while our watcher automatically rebuilds the bundle
function pullAndUpdate() {
    return execute('cd', ['/www/inferno-website'])
    .then(() => execute('git', ['pull']))
    .then(() => execute('pm2', ['restart', 'infernojs']))
}

// Run commands
function execute(cmd, args) {
    return new Promise((resolve) => {
        console.log('Command:', cmd, args.join(' '))

        const child = spawn(cmd, args);
        let output = '';

        child.stdout.on('data', function (buffer) { output += buffer.toString() });
        child.stdout.on('end', function() {
            console.log('Output:', output)
            resolve(output)
        });
    })
}
<|endoftext|>


<commit_before>var express = require('express');
var path = require('path');
var nodemailer = require('nodemailer');
var sendmailTransport = require('nodemailer-sendmail-transport');

var routes = express();
var options = {
    path: '/usr/lib/sendmail'
}

var transporter = nodemailer.createTransport(sendmailTransport(options))

routes.use(function(req, res, next) {
    console.log('----- Request for AFrame received.-----');
    console.log('req.url: ' + req.url);
    console.log('req.baseUrl: ' + req.baseUrl);
    console.log('req.method: ' + req.method);
    console.log('req.originalUrl: ' + req.originalUrl);
    console.log('req.hostname: ' + req.hostname);
    console.log('req.params: ' + JSON.stringify(req.params));
    console.log('req.path' + req.path);
    console.log('---------------------------------------');
    next();
});

routes.post('/mail', function(req, res, next) {
    console.log('Attempting to send email.');
    var mailOptions = {
        from: '"A-Frame Website" contactform@aframe.com',
        to: 'david.a.leroy@gmail.com',
        subject: 'TEST',
        text: 'TESTING the contact form'
    };

    transporter.sendMail(mailOptions, function(error, info) {
        if (error) {
            res.send(500);
            console.log('Message failed.');
            return console.log(error);
        }
        console.log('Message sent: ' + info.response);

        res.send(200);
    });
});

module.exports = routes;
<commit_msg>Hookup email to send to a frame office. update logging statements.<commit_after>var express = require('express');
var path = require('path');
var nodemailer = require('nodemailer');
var sendmailTransport = require('nodemailer-sendmail-transport');

var routes = express();
var options = {
    path: '/usr/lib/sendmail'
}

var transporter = nodemailer.createTransport(sendmailTransport(options))

routes.use(function(req, res, next) {
    console.log('----- Request for AFrame received.-----');
    console.log('req.method: ' + req.method);
    console.log('req.params: ' + JSON.stringify(req.params));
    console.log('req.path: ' + req.path);
    console.log('req.body: ' + JSON.stringify(req.body));
    console.log('---------------------------------------');
    next();
});

routes.post('/mail', function(req, res, next) {
    var mailOptions = {};
    console.log('Attempting to send email.');

    if (!(body && body.name && body.email && body.message)) {
        res.send(500);
        return console.log('req.body is missing the required values.');
    }

    mailOptions = {
        from: req.body.name,
        to: 'a.frame.office@gmail.com',
        subject: 'A-frame website contact form.',
        text: req.body.message
    };

    transporter.sendMail(mailOptions, function(error, info) {
        if (error) {
            res.send(500);
            console.log('Message failed.');
            return console.log(error);
        }

        res.send(200);
        console.log('Message sent: ' + info.response);
    });
});

module.exports = routes;
<|endoftext|>


<commit_before>VIE.prototype.RdfaService = function(options) {
    if (!options) {
        options = {};
    }
    this.vie = null;
    this.name = 'rdfa';
    this.subjectSelector = options.subjectSelector ? options.subjectSelector : "[about],[typeof],[src],[href],html";
    this.predicateSelector = options.predicateSelector ? options.predicateSelector : "[property],[rel]";
    this.views = [];
};

VIE.prototype.RdfaService.prototype = {

    load : function(loadable) {
        var service = this;
        var correct = loadable instanceof this.vie.Loadable;
        if (!correct) {
            throw "Invalid Loadable passed";
        }

        var element;
        if (!loadable.options.element) {
            if (typeof document === 'undefined') {
                return loadable.resolve([]);
            }
            element = jQuery(document);
        } else {
            element = loadable.options.element;
        }

        var ns = this.xmlns(element);
        for (var prefix in ns) {
            this.vie.namespaces.addOrReplace(prefix, ns[prefix]);
        }

        var entities = [];
        jQuery(this.subjectSelector, element).add(jQuery(element).filter(this.subjectSelector)).each(function() {
            var entity = service._readEntity(jQuery(this));
            if (entity) {
                entities.push(entity);
            }
        });
        loadable.resolve(entities);
    },

    save : function(savable) {
        var correct = savable instanceof this.vie.Savable;
        if (!correct) {
            throw "Invalid Savable passed";
        }

        if (!savable.options.element) {
            // FIXME: we could find element based on subject
            throw "Unable to write entity to RDFa, no element given";
        }

        if (!savable.options.entity) {
            throw "Unable to write to RDFa, no entity given";
        }

        this._writeEntity(savable.options.entity, savable.options.element);
        savable.resolve();
    },

    _readEntity : function(element) {
        var subject = this.getElementSubject(element);
        var type = this._getElementType(element);

        var entity = this._readEntityPredicates(subject, element, false);
        //if (jQuery.isEmptyObject(entity)) {
        //    return null;
        //}

        entity['@subject'] = subject;
        if (type) {
        	entity['@type'] = type;
        }

        var entityInstance = new this.vie.Entity(entity);
        entityInstance = this.vie.entities.addOrUpdate(entityInstance);
        this._registerEntityView(entityInstance, element);
        return entityInstance;
    },

    _writeEntity : function(entity, element) {
        var service = this;
        this._findPredicateElements(this.getElementSubject(element), element, true).each(function() {
            var predicateElement = jQuery(this);
            var predicate = service.getElementPredicate(predicateElement);
            if (!entity.has(predicate)) {
                return true;
            }

            var value = entity.get(predicate);
            if (value === service.readElementValue(predicate, predicateElement)) {
                return true;
            }

            service.writeElementValue(predicate, predicateElement, value);
        });
        return true;
    },

    _getViewForElement : function(element) {
        var viewInstance;
        jQuery.each(this.views, function() {
            if (this.el.get(0) === element.get(0)) {
                viewInstance = this;
                return false;
            }
        });
        return viewInstance;
    },

    _registerEntityView : function(entity, element) {
        var service = this;
        var viewInstance = this._getViewForElement(element);
        if (viewInstance) {
            return viewInstance;
        }

        viewInstance = new this.vie.view.Entity({
            model: entity,
            el: element,
            tagName: element.get(0).nodeName,
            vie: this.vie,
            service: this.name
        });
        this.views.push(viewInstance);

        // Find collection elements and create collection views for them
        _.each(entity.attributes, function(value, predicate) {
            var attributeValue = entity.get(predicate);
            if (attributeValue instanceof service.vie.Collection) {
                jQuery.each(service.getElementByPredicate(predicate, element), function() {
                    service._registerCollectionView(attributeValue, jQuery(this));
                });
            }
        });
        return viewInstance;
    },

    _registerCollectionView : function(collection, element) {
        var viewInstance = this._getViewForElement(element);
        if (viewInstance) {
            return viewInstance;
        }

        var entityTemplate = element.children(':first-child');

        viewInstance = new this.vie.view.Collection({
            collection: collection,
            model: collection.model,
            el: element,
            template: entityTemplate,
            service: this,
            tagName: element.get(0).nodeName
        });
        this.views.push(viewInstance);
        return viewInstance;
    },

    _getElementType : function (element) {
    	var type;
     	if (jQuery(element).attr('typeof')) {
	     	type = jQuery(element).attr('typeof');
		     	if (type.indexOf("://") !== -1) {
		     	return "<" + type + ">";
	     	}
	     	else {
		     	return type;
	     	}
     	}
     	return null;
     },

    getElementSubject : function(element) {
        if (typeof document !== 'undefined') {
            if (element === document) {
                return document.baseURI;
            }
        }
        var subject = undefined;
        jQuery(element).closest(this.subjectSelector).each(function() {
            if (jQuery(this).attr('about')) {
                subject = jQuery(this).attr('about');
                return true;
            }
            if (jQuery(this).attr('src')) {
                subject = jQuery(this).attr('src');
                return true;
            }
            if (jQuery(this).attr('typeof')) {
                subject = this;
                return true;
            }

            // We also handle baseURL outside browser context by manually
            // looking for the `<base>` element inside HTML head.
            if (jQuery(this).get(0).nodeName === 'HTML') {
                jQuery(this).find('base').each(function() {
                    subject = jQuery(this).attr('href');
                });
            }
        });

        if (!subject) {
            return undefined;
        }

        if (typeof subject === 'object') {
            return subject;
        }

        return "<" + subject + ">";
    },

    setElementSubject : function(subject, element) {
        if (jQuery(element).attr('src')) {
            return jQuery(element).attr('src', subject);
        }
        return jQuery(element).attr('about', subject);
    },

    getElementPredicate : function(element) {
        var predicate;
        predicate = element.attr('property');
        if (!predicate) {
            predicate = element.attr('rel');
        }
        return predicate;
    },

    getElementBySubject : function(subject, element) {
        var service = this;
        return jQuery(element).find(this.subjectSelector).add(jQuery(element).filter(this.subjectSelector)).filter(function() {
            if (service.getElementSubject(jQuery(this)) !== subject) {
                return false;
            }

            return true;
        });
    },

    getElementByPredicate : function(predicate, element) {
        var service = this;
        var subject = this.getElementSubject(element);
        return jQuery(element).find(this.predicateSelector).add(jQuery(element).filter(this.predicateSelector)).filter(function() {
            if (service.getElementPredicate(jQuery(this)) !== predicate) {
                return false;
            }

            if (service.getElementSubject(jQuery(this)) !== subject) {
                return false;
            }

            return true;
        });
    },

    _readEntityPredicates : function(subject, element, emptyValues) {
        var service = this;
        var entityPredicates = {};

        this._findPredicateElements(subject, element, true).each(function() {
            var predicateElement = jQuery(this);
            var predicate = service.getElementPredicate(predicateElement);
            var value = service.readElementValue(predicate, predicateElement);

            if (value === null && !emptyValues) {
                return;
            }

            entityPredicates[predicate] = value;
        });

        if (jQuery(element).get(0).tagName !== 'HTML') {
            jQuery(element).parent('[rev]').each(function() {
                entityPredicates[jQuery(this).attr('rev')] = service.getElementSubject(this);
            });
        }

        return entityPredicates;
    },

    _findPredicateElements : function(subject, element, allowNestedPredicates) {
        var service = this;
        return jQuery(element).find(this.predicateSelector).add(jQuery(element).filter(this.predicateSelector)).filter(function() {
            if (service.getElementSubject(this) !== subject) {
                return false;
            }
            if (!allowNestedPredicates) {
                if (!jQuery(this).parents('[property]').length) {
                    return true;
                }
                return false;
            }

            return true;
        });
    },

    readElementValue : function(predicate, element) {
        // The `content` attribute can be used for providing machine-readable
        // values for elements where the HTML presentation differs from the
        // actual value.
        var content = element.attr('content');
        if (content) {
            return content;
        }

        // The `resource` attribute can be used to link a predicate to another
        // RDF resource.
        var resource = element.attr('resource');
        if (resource) {
            return "<" + resource + ">";
        }

        // `href` attribute also links to another RDF resource.
        var href = element.attr('href');
        if (href && element.attr('rel') === predicate) {
            return "<" + href + ">";
        }

        // If the predicate is a relation, we look for identified child objects
        // and provide their identifiers as the values. To protect from scope
        // creep, we only support direct descentants of the element where the
        // `rel` attribute was set.
        if (element.attr('rel')) {
            var value = [];
            var service = this;
            jQuery(element).children(this.subjectSelector).each(function() {
                value.push(service.getElementSubject(this));
            });
            return value;
        }

        // If none of the checks above matched we return the HTML contents of
        // the element as the literal value.
        return element.html();
    },

    writeElementValue : function(predicate, element, value) {

    	//TODO: this is a hack, please fix!
     	if (value instanceof Array && value.length > 0) value = value[0];

     	// The `content` attribute can be used for providing machine-readable
        // values for elements where the HTML presentation differs from the
        // actual value.
        var content = element.attr('content');
        if (content) {
            element.attr('content', value);
            return;
        }

        // The `resource` attribute can be used to link a predicate to another
        // RDF resource.
        var resource = element.attr('resource');
        if (resource) {
            element.attr('resource', value);
        }

        // Property has inline value. Change the HTML contents of the property
        // element to match the new value.
        element.html(value);
    },

    // mostyl copied from http://code.google.com/p/rdfquery/source/browse/trunk/jquery.xmlns.js
    xmlns : function (elem) {
        var $elem;
        if (!elem) {
            if (typeof document === 'undefined') {
                return {};
            }
            $elem = jQuery(document);
        } else {
            $elem = jQuery(elem);
        }

        var obj = {};

        $elem.each(function (i, e) {
            if (e.attributes && e.attributes.getNamedItemNS) {
                for (i = 0; i < e.attributes.length; i += 1) {
                    var attr = e.attributes[i];
                    if (/^xmlns(:(.+))?$/.test(attr.nodeName)) {
                        prefix = /^xmlns(:(.+))?$/.exec(attr.nodeName)[2] || '';
                        value = attr.nodeValue;
                        if (prefix === '' || value !== '') {
                            obj[prefix] = attr.nodeValue;
                        }
                    }
                }
            }
        });

        return obj;
    }

};
<commit_msg>Some fixes for collection and CURIE handling<commit_after>VIE.prototype.RdfaService = function(options) {
    if (!options) {
        options = {};
    }
    this.vie = null;
    this.name = 'rdfa';
    this.subjectSelector = options.subjectSelector ? options.subjectSelector : "[about],[typeof],[src],[href],html";
    this.predicateSelector = options.predicateSelector ? options.predicateSelector : "[property],[rel]";
    this.views = [];
};

VIE.prototype.RdfaService.prototype = {

    load : function(loadable) {
        var service = this;
        var correct = loadable instanceof this.vie.Loadable;
        if (!correct) {
            throw "Invalid Loadable passed";
        }

        var element;
        if (!loadable.options.element) {
            if (typeof document === 'undefined') {
                return loadable.resolve([]);
            }
            element = jQuery(document);
        } else {
            element = loadable.options.element;
        }

        var ns = this.xmlns(element);
        for (var prefix in ns) {
            this.vie.namespaces.addOrReplace(prefix, ns[prefix]);
        }

        var entities = [];
        jQuery(this.subjectSelector, element).add(jQuery(element).filter(this.subjectSelector)).each(function() {
            var entity = service._readEntity(jQuery(this));
            if (entity) {
                entities.push(entity);
            }
        });
        loadable.resolve(entities);
    },

    save : function(savable) {
        var correct = savable instanceof this.vie.Savable;
        if (!correct) {
            throw "Invalid Savable passed";
        }

        if (!savable.options.element) {
            // FIXME: we could find element based on subject
            throw "Unable to write entity to RDFa, no element given";
        }

        if (!savable.options.entity) {
            throw "Unable to write to RDFa, no entity given";
        }

        this._writeEntity(savable.options.entity, savable.options.element);
        savable.resolve();
    },

    _readEntity : function(element) {
        var subject = this.getElementSubject(element);
        var type = this._getElementType(element);
        var predicate, value, valueCollection;

        var entity = this._readEntityPredicates(subject, element, false);
        //if (jQuery.isEmptyObject(entity)) {
        //    return null;
        //}

        for (predicate in entity) {
            value = entity[predicate];
            if (typeof value !== "object" || toString.call(value) !== '[object Array]') {
                continue;
            }
            valueCollection = new this.vie.Collection();
            _.each(value, function(valueItem) {
                valueCollection.addOrUpdate({'@subject': valueItem});
            });
            entity[predicate] = valueCollection;
        }

        entity['@subject'] = subject;
        if (type) {
        	entity['@type'] = type;
        }

        var entityInstance = new this.vie.Entity(entity);
        entityInstance = this.vie.entities.addOrUpdate(entityInstance);
        this._registerEntityView(entityInstance, element);
        return entityInstance;
    },

    _writeEntity : function(entity, element) {
        var service = this;
        this._findPredicateElements(this.getElementSubject(element), element, true).each(function() {
            var predicateElement = jQuery(this);
            var predicate = service.getElementPredicate(predicateElement);
            if (!entity.has(predicate)) {
                return true;
            }

            var value = entity.get(predicate);
            if (value === service.readElementValue(predicate, predicateElement)) {
                return true;
            }

            service.writeElementValue(predicate, predicateElement, value);
        });
        return true;
    },

    _getViewForElement : function(element, collectionView) {
        var viewInstance;
        jQuery.each(this.views, function() {
            if (this.el.get(0) === element.get(0)) {
                if (collectionView && !this.template) {
                    return true;
                }
                viewInstance = this;
                return false;
            }
        });
        return viewInstance;
    },

    _registerEntityView : function(entity, element) {
        var service = this;
        var viewInstance = this._getViewForElement(element);
        if (viewInstance) {
            return viewInstance;
        }

        viewInstance = new this.vie.view.Entity({
            model: entity,
            el: element,
            tagName: element.get(0).nodeName,
            vie: this.vie,
            service: this.name
        });
        this.views.push(viewInstance);

        // Find collection elements and create collection views for them
        _.each(entity.attributes, function(value, predicate) {
            var attributeValue = entity.fromReference(entity.get(predicate));
            if (attributeValue instanceof service.vie.Collection) {
                jQuery.each(service.getElementByPredicate(predicate, element), function() {
                    service._registerCollectionView(attributeValue, jQuery(this));
                });
            }
        });
        return viewInstance;
    },

    _registerCollectionView : function(collection, element) {
        var viewInstance = this._getViewForElement(element, true);
        if (viewInstance) {
            return viewInstance;
        }

        var entityTemplate = element.children(':first-child');

        viewInstance = new this.vie.view.Collection({
            collection: collection,
            model: collection.model,
            el: element,
            template: entityTemplate,
            service: this,
            tagName: element.get(0).nodeName
        });
        this.views.push(viewInstance);
        return viewInstance;
    },

    _getElementType : function (element) {
    	var type;
     	if (jQuery(element).attr('typeof')) {
	     	type = jQuery(element).attr('typeof');
		     	if (type.indexOf("://") !== -1) {
		     	return "<" + type + ">";
	     	}
	     	else {
		     	return type;
	     	}
     	}
     	return null;
     },

    getElementSubject : function(element) {
        if (typeof document !== 'undefined') {
            if (element === document) {
                return document.baseURI;
            }
        }
        var subject = undefined;
        jQuery(element).closest(this.subjectSelector).each(function() {
            if (jQuery(this).attr('about')) {
                subject = jQuery(this).attr('about');
                return true;
            }
            if (jQuery(this).attr('src')) {
                subject = jQuery(this).attr('src');
                return true;
            }
            if (jQuery(this).attr('typeof')) {
                subject = this;
                return true;
            }

            // We also handle baseURL outside browser context by manually
            // looking for the `<base>` element inside HTML head.
            if (jQuery(this).get(0).nodeName === 'HTML') {
                jQuery(this).find('base').each(function() {
                    subject = jQuery(this).attr('href');
                });
            }
        });

        if (!subject) {
            return undefined;
        }

        if (typeof subject === 'object') {
            return subject;
        }

        return "<" + subject + ">";
    },

    setElementSubject : function(subject, element) {
        if (jQuery(element).attr('src')) {
            return jQuery(element).attr('src', subject);
        }
        return jQuery(element).attr('about', subject);
    },

    getElementPredicate : function(element) {
        var predicate;
        predicate = element.attr('property');
        if (!predicate) {
            predicate = element.attr('rel');
        }
        return predicate;
    },

    getElementBySubject : function(subject, element) {
        var service = this;
        return jQuery(element).find(this.subjectSelector).add(jQuery(element).filter(this.subjectSelector)).filter(function() {
            if (service.getElementSubject(jQuery(this)) !== subject) {
                return false;
            }

            return true;
        });
    },

    getElementByPredicate : function(predicate, element) {
        var service = this;
        var subject = this.getElementSubject(element);
        return jQuery(element).find(this.predicateSelector).add(jQuery(element).filter(this.predicateSelector)).filter(function() {
            var foundPredicate = service.getElementPredicate(jQuery(this));
            if (service.vie.namespaces.curie(foundPredicate) !== service.vie.namespaces.curie(predicate)) {
                return false;
            }

            if (service.getElementSubject(jQuery(this)) !== subject) {
                return false;
            }

            return true;
        });
    },

    _readEntityPredicates : function(subject, element, emptyValues) {
        var service = this;
        var entityPredicates = {};

        this._findPredicateElements(subject, element, true).each(function() {
            var predicateElement = jQuery(this);
            var predicate = service.getElementPredicate(predicateElement);
            var value = service.readElementValue(predicate, predicateElement);

            if (value === null && !emptyValues) {
                return;
            }

            entityPredicates[predicate] = value;
        });

        if (jQuery(element).get(0).tagName !== 'HTML') {
            jQuery(element).parent('[rev]').each(function() {
                entityPredicates[jQuery(this).attr('rev')] = service.getElementSubject(this);
            });
        }

        return entityPredicates;
    },

    _findPredicateElements : function(subject, element, allowNestedPredicates) {
        var service = this;
        return jQuery(element).find(this.predicateSelector).add(jQuery(element).filter(this.predicateSelector)).filter(function() {
            if (service.getElementSubject(this) !== subject) {
                return false;
            }
            if (!allowNestedPredicates) {
                if (!jQuery(this).parents('[property]').length) {
                    return true;
                }
                return false;
            }

            return true;
        });
    },

    readElementValue : function(predicate, element) {
        // The `content` attribute can be used for providing machine-readable
        // values for elements where the HTML presentation differs from the
        // actual value.
        var content = element.attr('content');
        if (content) {
            return content;
        }

        // The `resource` attribute can be used to link a predicate to another
        // RDF resource.
        var resource = element.attr('resource');
        if (resource) {
            return "<" + resource + ">";
        }

        // `href` attribute also links to another RDF resource.
        var href = element.attr('href');
        if (href && element.attr('rel') === predicate) {
            return "<" + href + ">";
        }

        // If the predicate is a relation, we look for identified child objects
        // and provide their identifiers as the values. To protect from scope
        // creep, we only support direct descentants of the element where the
        // `rel` attribute was set.
        if (element.attr('rel')) {
            var value = [];
            var service = this;
            jQuery(element).children(this.subjectSelector).each(function() {
                value.push(service.getElementSubject(this));
            });
            return value;
        }

        // If none of the checks above matched we return the HTML contents of
        // the element as the literal value.
        return element.html();
    },

    writeElementValue : function(predicate, element, value) {

    	//TODO: this is a hack, please fix!
     	if (value instanceof Array && value.length > 0) value = value[0];

     	// The `content` attribute can be used for providing machine-readable
        // values for elements where the HTML presentation differs from the
        // actual value.
        var content = element.attr('content');
        if (content) {
            element.attr('content', value);
            return;
        }

        // The `resource` attribute can be used to link a predicate to another
        // RDF resource.
        var resource = element.attr('resource');
        if (resource) {
            element.attr('resource', value);
        }

        // Property has inline value. Change the HTML contents of the property
        // element to match the new value.
        element.html(value);
    },

    // mostyl copied from http://code.google.com/p/rdfquery/source/browse/trunk/jquery.xmlns.js
    xmlns : function (elem) {
        var $elem;
        if (!elem) {
            if (typeof document === 'undefined') {
                return {};
            }
            $elem = jQuery(document);
        } else {
            $elem = jQuery(elem);
        }

        var obj = {};

        $elem.each(function (i, e) {
            if (e.attributes && e.attributes.getNamedItemNS) {
                for (i = 0; i < e.attributes.length; i += 1) {
                    var attr = e.attributes[i];
                    if (/^xmlns(:(.+))?$/.test(attr.nodeName)) {
                        prefix = /^xmlns(:(.+))?$/.exec(attr.nodeName)[2] || '';
                        value = attr.nodeValue;
                        if (prefix === '' || value !== '') {
                            obj[prefix] = attr.nodeValue;
                        }
                    }
                }
            }
        });

        return obj;
    }

};
<|endoftext|>


<commit_before>/**
 * Copyright 2016 The AMP HTML Authors. All Rights Reserved.
 *
 * 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.
 */

import {Services} from '../services';
import {base64UrlEncodeFromBytes} from '../utils/base64';
import {dev} from '../log';
import {getService, registerServiceBuilder} from '../service';
import {stringToBytes, utf8Encode} from '../utils/bytes';

/** @const {string} */
const TAG = 'Crypto';
const FALLBACK_MSG = 'SubtleCrypto failed, fallback to closure lib.';

/**
 * @typedef {function((string|Uint8Array))}
 */
let CryptoPolyfillDef;

export class Crypto {
  /**
   * Creates an instance of Crypto.
   * @param {!Window} win
   */
  constructor(win) {
    /** @private {!Window} */
    this.win_ = win;

    let subtle = null;
    let isLegacyWebkit = false;
    if (win.crypto) {
      if (win.crypto.subtle) {
        subtle = win.crypto.subtle;
      } else if (win.crypto.webkitSubtle) {
        subtle = win.crypto.webkitSubtle;
        isLegacyWebkit = true;
      }
    }

    /** @const {{name: string}} */
    this.pkcsAlgo = {
      name: 'RSASSA-PKCS1-v1_5',
      hash: {name: 'SHA-256'},
    };

    /** @const {?webCrypto.SubtleCrypto} */
    this.subtle = subtle;

    /** @private @const {boolean} */
    this.isLegacyWebkit_ = isLegacyWebkit;

    /** @private {?Promise<!CryptoPolyfillDef>} */
    this.polyfillPromise_ = null;
  }

  /**
   * Returns the SHA-384 hash of the input string in a number array.
   * Input string cannot contain chars out of range [0,255].
   * @param {string|!Uint8Array} input
   * @return {!Promise<!Uint8Array>}
   * @throws {!Error} when input string contains chars out of range [0,255]
   */
  sha384(input) {
    if (typeof input === 'string') {
      input = stringToBytes(input);
    }

    if (!this.subtle || this.polyfillPromise_) {
      // means native Crypto API is not available or failed before.
      return (this.polyfillPromise_ || this.loadPolyfill_())
          .then(polyfillSha384 => polyfillSha384(input));
    }

    try {
      return this.subtle.digest({name: 'SHA-384'}, input)
          /** @param {?} buffer */
          .then(buffer => new Uint8Array(buffer),
              e => {
                // Chrome doesn't allow the usage of Crypto API under
                // non-secure origin: https://www.chromium.org/Home/chromium-security/prefer-secure-origins-for-powerful-new-features
                if (e.message && e.message.indexOf('secure origin') < 0) {
                  // Log unexpected fallback.
                  dev().error(TAG, FALLBACK_MSG, e);
                }
                return this.loadPolyfill_().then(() => this.sha384(input));
              });
    } catch (e) {
      dev().error(TAG, FALLBACK_MSG, e);
      return this.loadPolyfill_().then(() => this.sha384(input));
    }
  }

  /**
   * Returns the SHA-384 hash of the input string in the format of web safe
   * base64 (using -_. instead of +/=).
   * Input string cannot contain chars out of range [0,255].
   * @param {string|!Uint8Array} input
   * @return {!Promise<string>}
   * @throws {!Error} when input string contains chars out of range [0,255]
   */
  sha384Base64(input) {
    return this.sha384(input).then(buffer => base64UrlEncodeFromBytes(buffer));
  }

  /**
   * Returns a uniform hash of the input string as a float number in the range
   * of [0, 1).
   * Input string cannot contain chars out of range [0,255].
   * @param {string|!Uint8Array} input
   * @return {!Promise<number>}
   */
  uniform(input) {
    return this.sha384(input).then(buffer => {
      // Consider the Uint8 array as a base256 fraction number,
      // then convert it to the decimal form.
      let result = 0;
      for (let i = 2; i >= 0; i--) { // 3 base256 digits give enough precision
        result = (result + buffer[i]) / 256;
      }
      return result;
    });
  }

  /**
   * Loads Crypto polyfill library.
   * @return {!Promise<!CryptoPolyfillDef>}
   * @private
   */
  loadPolyfill_() {
    if (this.polyfillPromise_) {
      return this.polyfillPromise_;
    }
    return this.polyfillPromise_ = Services.extensionsFor(this.win_)
        .preloadExtension('amp-crypto-polyfill')
        .then(() => getService(this.win_, 'crypto-polyfill'));
  }

  /**
   * Checks whether Web Cryptography is available, which is required for PKCS 1
   * operations. SHA-384 operations do not need this because there's a polyfill.
   * This could be false if the browser does not support Web Cryptography, or if
   * the current browsing context is not secure (e.g., it's on an insecure HTTP
   * page, or an HTTPS iframe embedded in an insecure HTTP page).
   *
   * @return {boolean} whether Web Cryptography is available
   */
  isPkcsAvailable() {
    return Boolean(this.subtle) && this.win_['isSecureContext'] !== false;
  }

  /**
   * Converts an RSA JSON Web Key object to a browser-native cryptographic key.
   * As a precondition, `isPkcsAvailable()` must be `true`.
   *
   * @param {!Object} jwk a deserialized RSA JSON Web Key, as specified in
   *     Section 6.3 of RFC 7518
   * @return {!Promise<!webCrypto.CryptoKey>}
   * @throws {TypeError} if `jwk` is not an RSA JSON Web Key
   */
  importPkcsKey(jwk) {
    dev().assert(this.isPkcsAvailable());
    // Safari 10 and earlier want this as an ArrayBufferView.
    const keyData = this.isLegacyWebkit_
      ? utf8Encode(JSON.stringify(/** @type {!JsonObject} */ (jwk)))
      : /** @type {!webCrypto.JsonWebKey} */ (jwk);
    return /** @type {!Promise<!webCrypto.CryptoKey>} */ (
      this.subtle.importKey('jwk', keyData, this.pkcsAlgo, true, ['verify'])
    );
  }

  /**
   * Verifies an RSASSA-PKCS1-v1_5 signature with a SHA-256 hash. As a
   * precondition, `isPkcsAvailable()` must be `true`.
   *
   * @param {!webCrypto.CryptoKey} key an RSA public key
   * @param {!Uint8Array} signature an RSASSA-PKCS1-v1_5 signature
   * @param {!BufferSource} data the data that was signed
   * @return {!Promise<boolean>} whether the signature is correct for the given
   *     data and public key
   */
  verifyPkcs(key, signature, data) {
    dev().assert(this.isPkcsAvailable());
    return /** @type {!Promise<boolean>} */ (
      this.subtle.verify(this.pkcsAlgo, key, signature, data)
    );
  }
}

/**
 * @param {!Window} win
 */
export function installCryptoService(win) {
  return registerServiceBuilder(win, 'crypto', Crypto);
}
<commit_msg>Change crypto fallback message to user error (#18882)<commit_after>/**
 * Copyright 2016 The AMP HTML Authors. All Rights Reserved.
 *
 * 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.
 */

import {Services} from '../services';
import {base64UrlEncodeFromBytes} from '../utils/base64';
import {dev, user} from '../log';
import {getService, registerServiceBuilder} from '../service';
import {stringToBytes, utf8Encode} from '../utils/bytes';

/** @const {string} */
const TAG = 'Crypto';
const FALLBACK_MSG = 'SubtleCrypto failed, fallback to closure lib.';

/**
 * @typedef {function((string|Uint8Array))}
 */
let CryptoPolyfillDef;

export class Crypto {
  /**
   * Creates an instance of Crypto.
   * @param {!Window} win
   */
  constructor(win) {
    /** @private {!Window} */
    this.win_ = win;

    let subtle = null;
    let isLegacyWebkit = false;
    if (win.crypto) {
      if (win.crypto.subtle) {
        subtle = win.crypto.subtle;
      } else if (win.crypto.webkitSubtle) {
        subtle = win.crypto.webkitSubtle;
        isLegacyWebkit = true;
      }
    }

    /** @const {{name: string}} */
    this.pkcsAlgo = {
      name: 'RSASSA-PKCS1-v1_5',
      hash: {name: 'SHA-256'},
    };

    /** @const {?webCrypto.SubtleCrypto} */
    this.subtle = subtle;

    /** @private @const {boolean} */
    this.isLegacyWebkit_ = isLegacyWebkit;

    /** @private {?Promise<!CryptoPolyfillDef>} */
    this.polyfillPromise_ = null;
  }

  /**
   * Returns the SHA-384 hash of the input string in a number array.
   * Input string cannot contain chars out of range [0,255].
   * @param {string|!Uint8Array} input
   * @return {!Promise<!Uint8Array>}
   * @throws {!Error} when input string contains chars out of range [0,255]
   */
  sha384(input) {
    if (typeof input === 'string') {
      input = stringToBytes(input);
    }

    if (!this.subtle || this.polyfillPromise_) {
      // means native Crypto API is not available or failed before.
      return (this.polyfillPromise_ || this.loadPolyfill_())
          .then(polyfillSha384 => polyfillSha384(input));
    }

    try {
      return this.subtle.digest({name: 'SHA-384'}, input)
          /** @param {?} buffer */
          .then(buffer => new Uint8Array(buffer),
              e => {
                // Chrome doesn't allow the usage of Crypto API under
                // non-secure origin: https://www.chromium.org/Home/chromium-security/prefer-secure-origins-for-powerful-new-features
                if (e.message && e.message.indexOf('secure origin') < 0) {
                  // Log unexpected fallback.
                  user().error(TAG, FALLBACK_MSG, e);
                }
                return this.loadPolyfill_().then(() => this.sha384(input));
              });
    } catch (e) {
      dev().error(TAG, FALLBACK_MSG, e);
      return this.loadPolyfill_().then(() => this.sha384(input));
    }
  }

  /**
   * Returns the SHA-384 hash of the input string in the format of web safe
   * base64 (using -_. instead of +/=).
   * Input string cannot contain chars out of range [0,255].
   * @param {string|!Uint8Array} input
   * @return {!Promise<string>}
   * @throws {!Error} when input string contains chars out of range [0,255]
   */
  sha384Base64(input) {
    return this.sha384(input).then(buffer => base64UrlEncodeFromBytes(buffer));
  }

  /**
   * Returns a uniform hash of the input string as a float number in the range
   * of [0, 1).
   * Input string cannot contain chars out of range [0,255].
   * @param {string|!Uint8Array} input
   * @return {!Promise<number>}
   */
  uniform(input) {
    return this.sha384(input).then(buffer => {
      // Consider the Uint8 array as a base256 fraction number,
      // then convert it to the decimal form.
      let result = 0;
      for (let i = 2; i >= 0; i--) { // 3 base256 digits give enough precision
        result = (result + buffer[i]) / 256;
      }
      return result;
    });
  }

  /**
   * Loads Crypto polyfill library.
   * @return {!Promise<!CryptoPolyfillDef>}
   * @private
   */
  loadPolyfill_() {
    if (this.polyfillPromise_) {
      return this.polyfillPromise_;
    }
    return this.polyfillPromise_ = Services.extensionsFor(this.win_)
        .preloadExtension('amp-crypto-polyfill')
        .then(() => getService(this.win_, 'crypto-polyfill'));
  }

  /**
   * Checks whether Web Cryptography is available, which is required for PKCS 1
   * operations. SHA-384 operations do not need this because there's a polyfill.
   * This could be false if the browser does not support Web Cryptography, or if
   * the current browsing context is not secure (e.g., it's on an insecure HTTP
   * page, or an HTTPS iframe embedded in an insecure HTTP page).
   *
   * @return {boolean} whether Web Cryptography is available
   */
  isPkcsAvailable() {
    return Boolean(this.subtle) && this.win_['isSecureContext'] !== false;
  }

  /**
   * Converts an RSA JSON Web Key object to a browser-native cryptographic key.
   * As a precondition, `isPkcsAvailable()` must be `true`.
   *
   * @param {!Object} jwk a deserialized RSA JSON Web Key, as specified in
   *     Section 6.3 of RFC 7518
   * @return {!Promise<!webCrypto.CryptoKey>}
   * @throws {TypeError} if `jwk` is not an RSA JSON Web Key
   */
  importPkcsKey(jwk) {
    dev().assert(this.isPkcsAvailable());
    // Safari 10 and earlier want this as an ArrayBufferView.
    const keyData = this.isLegacyWebkit_
      ? utf8Encode(JSON.stringify(/** @type {!JsonObject} */ (jwk)))
      : /** @type {!webCrypto.JsonWebKey} */ (jwk);
    return /** @type {!Promise<!webCrypto.CryptoKey>} */ (
      this.subtle.importKey('jwk', keyData, this.pkcsAlgo, true, ['verify'])
    );
  }

  /**
   * Verifies an RSASSA-PKCS1-v1_5 signature with a SHA-256 hash. As a
   * precondition, `isPkcsAvailable()` must be `true`.
   *
   * @param {!webCrypto.CryptoKey} key an RSA public key
   * @param {!Uint8Array} signature an RSASSA-PKCS1-v1_5 signature
   * @param {!BufferSource} data the data that was signed
   * @return {!Promise<boolean>} whether the signature is correct for the given
   *     data and public key
   */
  verifyPkcs(key, signature, data) {
    dev().assert(this.isPkcsAvailable());
    return /** @type {!Promise<boolean>} */ (
      this.subtle.verify(this.pkcsAlgo, key, signature, data)
    );
  }
}

/**
 * @param {!Window} win
 */
export function installCryptoService(win) {
  return registerServiceBuilder(win, 'crypto', Crypto);
}
<|endoftext|>


<commit_before>angular.module('schemaForm').provider('schemaFormDecorators',
['$compileProvider', 'sfPathProvider', function($compileProvider, sfPathProvider) {
  var defaultDecorator = '';
  var directives = {};

  var templateUrl = function(name, form) {
    //schemaDecorator is alias for whatever is set as default
    if (name === 'sfDecorator') {
      name = defaultDecorator;
    }

    var directive = directives[name];

    //rules first
    var rules = directive.rules;
    for (var i = 0; i < rules.length; i++) {
      var res = rules[i](form);
      if (res) {
        return res;
      }
    }

    //then check mapping
    if (directive.mappings[form.type]) {
      return directive.mappings[form.type];
    }

    //try default
    return directive.mappings['default'];
  };

  var createDirective = function(name) {
    $compileProvider.directive(name,
      ['$parse', '$compile', '$http', '$templateCache', '$interpolate', '$q', 'sfErrorMessage', 'sfPath',
      function($parse,  $compile,  $http,  $templateCache, $interpolate, $q, sfErrorMessage, sfPath) {

        return {
          restrict: 'AE',
          replace: false,
          transclude: false,
          scope: true,
          require: '?^sfSchema',
          link: function(scope, element, attrs, sfSchema) {

            //The ngModelController is used in some templates and
            //is needed for error messages,
            scope.$on('schemaFormPropagateNgModelController', function(event, ngModel) {
              event.stopPropagation();
              event.preventDefault();
              scope.ngModel = ngModel;
            });

            //Keep error prone logic from the template
            scope.showTitle = function() {
              return scope.form && scope.form.notitle !== true && scope.form.title;
            };

            scope.listToCheckboxValues = function(list) {
              var values = {};
              angular.forEach(list, function(v) {
                values[v] = true;
              });
              return values;
            };

            scope.checkboxValuesToList = function(values) {
              var lst = [];
              angular.forEach(values, function(v, k) {
                if (v) {
                  lst.push(k);
                }
              });
              return lst;
            };

            scope.buttonClick = function($event, form) {
              if (angular.isFunction(form.onClick)) {
                form.onClick($event, form);
              } else if (angular.isString(form.onClick)) {
                if (sfSchema) {
                  //evaluating in scope outside of sfSchemas isolated scope
                  sfSchema.evalInParentScope(form.onClick, {'$event': $event, form: form});
                } else {
                  scope.$eval(form.onClick, {'$event': $event, form: form});
                }
              }
            };

            /**
             * Evaluate an expression, i.e. scope.$eval
             * but do it in sfSchemas parent scope sf-schema directive is used
             * @param {string} expression
             * @param {Object} locals (optional)
             * @return {Any} the result of the expression
             */
            scope.evalExpr = function(expression, locals) {
              if (sfSchema) {
                //evaluating in scope outside of sfSchemas isolated scope
                return sfSchema.evalInParentScope(expression, locals);
              }

              return scope.$eval(expression, locals);
            };

            /**
             * Evaluate an expression, i.e. scope.$eval
             * in this decorators scope
             * @param {string} expression
             * @param {Object} locals (optional)
             * @return {Any} the result of the expression
             */
            scope.evalInScope = function(expression, locals) {
              if (expression) {
                return scope.$eval(expression, locals);
              }
            };

            /**
             * Interpolate the expression.
             * Similar to `evalExpr()` and `evalInScope()`
             * but will not fail if the expression is
             * text that contains spaces.
             *
             * Use the Angular `{{ interpolation }}`
             * braces to access properties on `locals`.
             *
             * @param  {string} content The string to interpolate.
             * @param  {Object} locals (optional) Properties that may be accessed in the
             *                         `expression` string.
             * @return {Any} The result of the expression or `undefined`.
             */
            scope.interp = function(expression, locals) {
              return (expression && $interpolate(expression)(locals));
            };

            //This works since we ot the ngModel from the array or the schema-validate directive.
            scope.hasSuccess = function() {
              if (!scope.ngModel) {
                return false;
              }
              return scope.ngModel.$valid &&
                  (!scope.ngModel.$pristine || !scope.ngModel.$isEmpty(scope.ngModel.$modelValue));
            };

            scope.hasError = function() {
              if (!scope.ngModel) {
                return false;
              }
              return scope.ngModel.$invalid && !scope.ngModel.$pristine;
            };

            /**
             * DEPRECATED: use sf-messages instead.
             * Error message handler
             * An error can either be a schema validation message or a angular js validtion
             * error (i.e. required)
             */
            scope.errorMessage = function(schemaError) {
              return sfErrorMessage.interpolate(
                (schemaError && schemaError.code + '') || 'default',
                (scope.ngModel && scope.ngModel.$modelValue) || '',
                (scope.ngModel && scope.ngModel.$viewValue) || '',
                scope.form,
                scope.options && scope.options.validationMessage
              );
            };

            // Rebind our part of the form to the scope.
            var once = scope.$watch(attrs.form, function(form) {
              if (form) {
                // Workaround for 'updateOn' error from ngModelOptions
                // see https://github.com/Textalk/angular-schema-form/issues/255
                // and https://github.com/Textalk/angular-schema-form/issues/206
                form.ngModelOptions = form.ngModelOptions || {};
                scope.form  = form;

                //ok let's replace that template!
                //We do this manually since we need to bind ng-model properly and also
                //for fieldsets to recurse properly.
                var templatePromise;

                // type: "template" is a special case. It can contain a template inline or an url.
                // otherwise we find out the url to the template and load them.
                if (form.type === 'template' && form.template) {
                  templatePromise = $q.when(form.template);
                } else {
                  var url = form.type === 'template' ? form.templateUrl : templateUrl(name, form);
                  templatePromise = $http.get(url, {cache: $templateCache}).then(function(res) {
                                      return res.data;
                                    });
                }

                templatePromise.then(function(template) {
                  if (form.key) {
                    var key = form.key ?
                              sfPathProvider.stringify(form.key).replace(/"/g, '"') : '';
                    template = template.replace(
                      /\$\$value\$\$/g,
                      'model' + (key[0] !== '[' ? '.' : '') + key
                    );
                  }
                  element.html(template);

                  // Do we have a condition? Then we slap on an ng-if on all children,
                  // but be nice to existing ng-if.
                  if (form.condition) {

                    var evalExpr = 'evalExpr(form.condition,{ model: model, "arrayIndex": arrayIndex})';
                    if (form.key) {
                      evalExpr = 'evalExpr(form.condition,{ model: model, "arrayIndex": arrayIndex, "modelValue": model' + sfPath.stringify(form.key) + '})';
                    }

                    angular.forEach(element.children(), function(child) {
                      var ngIf = child.getAttribute('ng-if');
                      child.setAttribute(
                        'ng-if',
                        ngIf ?
                        '(' + ngIf +
                        ') || (' + evalExpr +')'
                        : evalExpr
                      );
                    });
                  }
                  $compile(element.contents())(scope);
                });

                // Where there is a key there is probably a ngModel
                if (form.key) {
                  // It looks better with dot notation.
                  scope.$on(
                    'schemaForm.error.' + form.key.join('.'),
                    function(event, error, validationMessage, validity) {
                      if (validationMessage === true || validationMessage === false) {
                        validity = validationMessage;
                        validationMessage = undefined;
                      }

                      if (scope.ngModel && error) {
                        if (scope.ngModel.$setDirty()) {
                          scope.ngModel.$setDirty();
                        } else {
                          // FIXME: Check that this actually works on 1.2
                          scope.ngModel.$dirty = true;
                          scope.ngModel.$pristine = false;
                        }

                        // Set the new validation message if one is supplied
                        // Does not work when validationMessage is just a string.
                        if (validationMessage) {
                          if (!form.validationMessage) {
                            form.validationMessage = {};
                          }
                          form.validationMessage[error] = validationMessage;
                        }

                        scope.ngModel.$setValidity(error, validity === true);

                        if (validity === true) {
                          // Setting or removing a validity can change the field to believe its valid
                          // but its not. So lets trigger its validation as well.
                          scope.$broadcast('schemaFormValidate');
                        }
                      }
                  })
                }

                once();
              }
            });
          }
        };
      }
    ]);
  };

  var createManualDirective = function(type, templateUrl, transclude) {
    transclude = angular.isDefined(transclude) ? transclude : false;
    $compileProvider.directive('sf' + angular.uppercase(type[0]) + type.substr(1), function() {
      return {
        restrict: 'EAC',
        scope: true,
        replace: true,
        transclude: transclude,
        template: '<sf-decorator form="form"></sf-decorator>',
        link: function(scope, element, attrs) {
          var watchThis = {
            'items': 'c',
            'titleMap': 'c',
            'schema': 'c'
          };
          var form = {type: type};
          var once = true;
          angular.forEach(attrs, function(value, name) {
            if (name[0] !== '$' && name.indexOf('ng') !== 0 && name !== 'sfField') {

              var updateForm = function(val) {
                if (angular.isDefined(val) && val !== form[name]) {
                  form[name] = val;

                  //when we have type, and if specified key we apply it on scope.
                  if (once && form.type && (form.key || angular.isUndefined(attrs.key))) {
                    scope.form = form;
                    once = false;
                  }
                }
              };

              if (name === 'model') {
                //"model" is bound to scope under the name "model" since this is what the decorators
                //know and love.
                scope.$watch(value, function(val) {
                  if (val && scope.model !== val) {
                    scope.model = val;
                  }
                });
              } else if (watchThis[name] === 'c') {
                //watch collection
                scope.$watchCollection(value, updateForm);
              } else {
                //$observe
                attrs.$observe(name, updateForm);
              }
            }
          });
        }
      };
    });
  };

  /**
   * Create a decorator directive and its sibling "manual" use directives.
   * The directive can be used to create form fields or other form entities.
   * It can be used in conjunction with <schema-form> directive in which case the decorator is
   * given it's configuration via a the "form" attribute.
   *
   * ex. Basic usage
   *   <sf-decorator form="myform"></sf-decorator>
   **
   * @param {string} name directive name (CamelCased)
   * @param {Object} mappings, an object that maps "type" => "templateUrl"
   * @param {Array}  rules (optional) a list of functions, function(form) {}, that are each tried in
   *                 turn,
   *                 if they return a string then that is used as the templateUrl. Rules come before
   *                 mappings.
   */
  this.createDecorator = function(name, mappings, rules) {
    directives[name] = {
      mappings: mappings || {},
      rules:    rules    || []
    };

    if (!directives[defaultDecorator]) {
      defaultDecorator = name;
    }
    createDirective(name);
  };

  /**
   * Creates a directive of a decorator
   * Usable when you want to use the decorators without using <schema-form> directive.
   * Specifically when you need to reuse styling.
   *
   * ex. createDirective('text','...')
   *  <sf-text title="foobar" model="person" key="name" schema="schema"></sf-text>
   *
   * @param {string}  type The type of the directive, resulting directive will have sf- prefixed
   * @param {string}  templateUrl
   * @param {boolean} transclude (optional) sets transclude option of directive, defaults to false.
   */
  this.createDirective = createManualDirective;

  /**
   * Same as createDirective, but takes an object where key is 'type' and value is 'templateUrl'
   * Useful for batching.
   * @param {Object} mappings
   */
  this.createDirectives = function(mappings) {
    angular.forEach(mappings, function(url, type) {
      createManualDirective(type, url);
    });
  };

  /**
   * Getter for directive mappings
   * Can be used to override a mapping or add a rule
   * @param {string} name (optional) defaults to defaultDecorator
   * @return {Object} rules and mappings { rules: [],mappings: {}}
   */
  this.directive = function(name) {
    name = name || defaultDecorator;
    return directives[name];
  };

  /**
   * Adds a mapping to an existing decorator.
   * @param {String} name Decorator name
   * @param {String} type Form type for the mapping
   * @param {String} url  The template url
   */
  this.addMapping = function(name, type, url) {
    if (directives[name]) {
      directives[name].mappings[type] = url;
    }
  };

  //Service is just a getter for directive mappings and rules
  this.$get = function() {
    return {
      directive: function(name) {
        return directives[name];
      },
      defaultDecorator: defaultDecorator
    };
  };

  //Create a default directive
  createDirective('sfDecorator');

}]);
<commit_msg>Try to fix the undefined modelValue caused by prevoius external validator. Fix #440<commit_after>angular.module('schemaForm').provider('schemaFormDecorators',
['$compileProvider', 'sfPathProvider', function($compileProvider, sfPathProvider) {
  var defaultDecorator = '';
  var directives = {};

  var templateUrl = function(name, form) {
    //schemaDecorator is alias for whatever is set as default
    if (name === 'sfDecorator') {
      name = defaultDecorator;
    }

    var directive = directives[name];

    //rules first
    var rules = directive.rules;
    for (var i = 0; i < rules.length; i++) {
      var res = rules[i](form);
      if (res) {
        return res;
      }
    }

    //then check mapping
    if (directive.mappings[form.type]) {
      return directive.mappings[form.type];
    }

    //try default
    return directive.mappings['default'];
  };

  var createDirective = function(name) {
    $compileProvider.directive(name,
      ['$parse', '$compile', '$http', '$templateCache', '$interpolate', '$q', 'sfErrorMessage', 'sfPath',
      function($parse,  $compile,  $http,  $templateCache, $interpolate, $q, sfErrorMessage, sfPath) {

        return {
          restrict: 'AE',
          replace: false,
          transclude: false,
          scope: true,
          require: '?^sfSchema',
          link: function(scope, element, attrs, sfSchema) {

            //The ngModelController is used in some templates and
            //is needed for error messages,
            scope.$on('schemaFormPropagateNgModelController', function(event, ngModel) {
              event.stopPropagation();
              event.preventDefault();
              scope.ngModel = ngModel;
            });

            //Keep error prone logic from the template
            scope.showTitle = function() {
              return scope.form && scope.form.notitle !== true && scope.form.title;
            };

            scope.listToCheckboxValues = function(list) {
              var values = {};
              angular.forEach(list, function(v) {
                values[v] = true;
              });
              return values;
            };

            scope.checkboxValuesToList = function(values) {
              var lst = [];
              angular.forEach(values, function(v, k) {
                if (v) {
                  lst.push(k);
                }
              });
              return lst;
            };

            scope.buttonClick = function($event, form) {
              if (angular.isFunction(form.onClick)) {
                form.onClick($event, form);
              } else if (angular.isString(form.onClick)) {
                if (sfSchema) {
                  //evaluating in scope outside of sfSchemas isolated scope
                  sfSchema.evalInParentScope(form.onClick, {'$event': $event, form: form});
                } else {
                  scope.$eval(form.onClick, {'$event': $event, form: form});
                }
              }
            };

            /**
             * Evaluate an expression, i.e. scope.$eval
             * but do it in sfSchemas parent scope sf-schema directive is used
             * @param {string} expression
             * @param {Object} locals (optional)
             * @return {Any} the result of the expression
             */
            scope.evalExpr = function(expression, locals) {
              if (sfSchema) {
                //evaluating in scope outside of sfSchemas isolated scope
                return sfSchema.evalInParentScope(expression, locals);
              }

              return scope.$eval(expression, locals);
            };

            /**
             * Evaluate an expression, i.e. scope.$eval
             * in this decorators scope
             * @param {string} expression
             * @param {Object} locals (optional)
             * @return {Any} the result of the expression
             */
            scope.evalInScope = function(expression, locals) {
              if (expression) {
                return scope.$eval(expression, locals);
              }
            };

            /**
             * Interpolate the expression.
             * Similar to `evalExpr()` and `evalInScope()`
             * but will not fail if the expression is
             * text that contains spaces.
             *
             * Use the Angular `{{ interpolation }}`
             * braces to access properties on `locals`.
             *
             * @param  {string} content The string to interpolate.
             * @param  {Object} locals (optional) Properties that may be accessed in the
             *                         `expression` string.
             * @return {Any} The result of the expression or `undefined`.
             */
            scope.interp = function(expression, locals) {
              return (expression && $interpolate(expression)(locals));
            };

            //This works since we ot the ngModel from the array or the schema-validate directive.
            scope.hasSuccess = function() {
              if (!scope.ngModel) {
                return false;
              }
              return scope.ngModel.$valid &&
                  (!scope.ngModel.$pristine || !scope.ngModel.$isEmpty(scope.ngModel.$modelValue));
            };

            scope.hasError = function() {
              if (!scope.ngModel) {
                return false;
              }
              return scope.ngModel.$invalid && !scope.ngModel.$pristine;
            };

            /**
             * DEPRECATED: use sf-messages instead.
             * Error message handler
             * An error can either be a schema validation message or a angular js validtion
             * error (i.e. required)
             */
            scope.errorMessage = function(schemaError) {
              return sfErrorMessage.interpolate(
                (schemaError && schemaError.code + '') || 'default',
                (scope.ngModel && scope.ngModel.$modelValue) || '',
                (scope.ngModel && scope.ngModel.$viewValue) || '',
                scope.form,
                scope.options && scope.options.validationMessage
              );
            };

            // Rebind our part of the form to the scope.
            var once = scope.$watch(attrs.form, function(form) {
              if (form) {
                // Workaround for 'updateOn' error from ngModelOptions
                // see https://github.com/Textalk/angular-schema-form/issues/255
                // and https://github.com/Textalk/angular-schema-form/issues/206
                form.ngModelOptions = form.ngModelOptions || {};
                scope.form  = form;

                //ok let's replace that template!
                //We do this manually since we need to bind ng-model properly and also
                //for fieldsets to recurse properly.
                var templatePromise;

                // type: "template" is a special case. It can contain a template inline or an url.
                // otherwise we find out the url to the template and load them.
                if (form.type === 'template' && form.template) {
                  templatePromise = $q.when(form.template);
                } else {
                  var url = form.type === 'template' ? form.templateUrl : templateUrl(name, form);
                  templatePromise = $http.get(url, {cache: $templateCache}).then(function(res) {
                                      return res.data;
                                    });
                }

                templatePromise.then(function(template) {
                  if (form.key) {
                    var key = form.key ?
                              sfPathProvider.stringify(form.key).replace(/"/g, '"') : '';
                    template = template.replace(
                      /\$\$value\$\$/g,
                      'model' + (key[0] !== '[' ? '.' : '') + key
                    );
                  }
                  element.html(template);

                  // Do we have a condition? Then we slap on an ng-if on all children,
                  // but be nice to existing ng-if.
                  if (form.condition) {

                    var evalExpr = 'evalExpr(form.condition,{ model: model, "arrayIndex": arrayIndex})';
                    if (form.key) {
                      evalExpr = 'evalExpr(form.condition,{ model: model, "arrayIndex": arrayIndex, "modelValue": model' + sfPath.stringify(form.key) + '})';
                    }

                    angular.forEach(element.children(), function(child) {
                      var ngIf = child.getAttribute('ng-if');
                      child.setAttribute(
                        'ng-if',
                        ngIf ?
                        '(' + ngIf +
                        ') || (' + evalExpr +')'
                        : evalExpr
                      );
                    });
                  }
                  $compile(element.contents())(scope);
                });

                // Where there is a key there is probably a ngModel
                if (form.key) {
                  // It looks better with dot notation.
                  scope.$on(
                    'schemaForm.error.' + form.key.join('.'),
                    function(event, error, validationMessage, validity) {
                      if (validationMessage === true || validationMessage === false) {
                        validity = validationMessage;
                        validationMessage = undefined;
                      }

                      if (scope.ngModel && error) {
                        if (scope.ngModel.$setDirty()) {
                          scope.ngModel.$setDirty();
                        } else {
                          // FIXME: Check that this actually works on 1.2
                          scope.ngModel.$dirty = true;
                          scope.ngModel.$pristine = false;
                        }

                        // Set the new validation message if one is supplied
                        // Does not work when validationMessage is just a string.
                        if (validationMessage) {
                          if (!form.validationMessage) {
                            form.validationMessage = {};
                          }
                          form.validationMessage[error] = validationMessage;
                        }

                        scope.ngModel.$setValidity(error, validity === true);

                        if (validity === true && !scope.ngModel.$valid) {
                          // Previous the model maybe invalid, then the model value is undefined.
                          // To make the validation to the tv4 works, we need also re-trigger validators
                          scope.ngModel.$validate();
                          // Setting or removing a validity can change the field to believe its valid
                          // but its not. So lets trigger its validation as well.
                          scope.$broadcast('schemaFormValidate');
                        }
                      }
                  })
                }

                once();
              }
            });
          }
        };
      }
    ]);
  };

  var createManualDirective = function(type, templateUrl, transclude) {
    transclude = angular.isDefined(transclude) ? transclude : false;
    $compileProvider.directive('sf' + angular.uppercase(type[0]) + type.substr(1), function() {
      return {
        restrict: 'EAC',
        scope: true,
        replace: true,
        transclude: transclude,
        template: '<sf-decorator form="form"></sf-decorator>',
        link: function(scope, element, attrs) {
          var watchThis = {
            'items': 'c',
            'titleMap': 'c',
            'schema': 'c'
          };
          var form = {type: type};
          var once = true;
          angular.forEach(attrs, function(value, name) {
            if (name[0] !== '$' && name.indexOf('ng') !== 0 && name !== 'sfField') {

              var updateForm = function(val) {
                if (angular.isDefined(val) && val !== form[name]) {
                  form[name] = val;

                  //when we have type, and if specified key we apply it on scope.
                  if (once && form.type && (form.key || angular.isUndefined(attrs.key))) {
                    scope.form = form;
                    once = false;
                  }
                }
              };

              if (name === 'model') {
                //"model" is bound to scope under the name "model" since this is what the decorators
                //know and love.
                scope.$watch(value, function(val) {
                  if (val && scope.model !== val) {
                    scope.model = val;
                  }
                });
              } else if (watchThis[name] === 'c') {
                //watch collection
                scope.$watchCollection(value, updateForm);
              } else {
                //$observe
                attrs.$observe(name, updateForm);
              }
            }
          });
        }
      };
    });
  };

  /**
   * Create a decorator directive and its sibling "manual" use directives.
   * The directive can be used to create form fields or other form entities.
   * It can be used in conjunction with <schema-form> directive in which case the decorator is
   * given it's configuration via a the "form" attribute.
   *
   * ex. Basic usage
   *   <sf-decorator form="myform"></sf-decorator>
   **
   * @param {string} name directive name (CamelCased)
   * @param {Object} mappings, an object that maps "type" => "templateUrl"
   * @param {Array}  rules (optional) a list of functions, function(form) {}, that are each tried in
   *                 turn,
   *                 if they return a string then that is used as the templateUrl. Rules come before
   *                 mappings.
   */
  this.createDecorator = function(name, mappings, rules) {
    directives[name] = {
      mappings: mappings || {},
      rules:    rules    || []
    };

    if (!directives[defaultDecorator]) {
      defaultDecorator = name;
    }
    createDirective(name);
  };

  /**
   * Creates a directive of a decorator
   * Usable when you want to use the decorators without using <schema-form> directive.
   * Specifically when you need to reuse styling.
   *
   * ex. createDirective('text','...')
   *  <sf-text title="foobar" model="person" key="name" schema="schema"></sf-text>
   *
   * @param {string}  type The type of the directive, resulting directive will have sf- prefixed
   * @param {string}  templateUrl
   * @param {boolean} transclude (optional) sets transclude option of directive, defaults to false.
   */
  this.createDirective = createManualDirective;

  /**
   * Same as createDirective, but takes an object where key is 'type' and value is 'templateUrl'
   * Useful for batching.
   * @param {Object} mappings
   */
  this.createDirectives = function(mappings) {
    angular.forEach(mappings, function(url, type) {
      createManualDirective(type, url);
    });
  };

  /**
   * Getter for directive mappings
   * Can be used to override a mapping or add a rule
   * @param {string} name (optional) defaults to defaultDecorator
   * @return {Object} rules and mappings { rules: [],mappings: {}}
   */
  this.directive = function(name) {
    name = name || defaultDecorator;
    return directives[name];
  };

  /**
   * Adds a mapping to an existing decorator.
   * @param {String} name Decorator name
   * @param {String} type Form type for the mapping
   * @param {String} url  The template url
   */
  this.addMapping = function(name, type, url) {
    if (directives[name]) {
      directives[name].mappings[type] = url;
    }
  };

  //Service is just a getter for directive mappings and rules
  this.$get = function() {
    return {
      directive: function(name) {
        return directives[name];
      },
      defaultDecorator: defaultDecorator
    };
  };

  //Create a default directive
  createDirective('sfDecorator');

}]);
<|endoftext|>