diff --git a/lib/firestore/encodeFirestoreValue.js b/lib/firestore/encodeFirestoreValue.js new file mode 100644 index 00000000..b4c42ffd --- /dev/null +++ b/lib/firestore/encodeFirestoreValue.js @@ -0,0 +1,80 @@ +'use strict'; + +var _ = require('lodash'); +var is = require('is'); + +var encodeFirestoreValue = function(data) { + var isPlainObject = function(input) { + return typeof input === 'object' && + input !== null && + _.isEqual(Object.getPrototypeOf(input), Object.prototype); + }; + + var encodeHelper = function(val) { + if (is.string(val)) { + return { + stringValue: val + }; + } + if (is.boolean(val)) { + return { + booleanValue: val + }; + } + if (is.integer(val)) { + return { + integerValue: val + }; + } + // Integers are handled above, the remaining numbers are treated as doubles + if (is.number(val)) { + return { + doubleValue: val + }; + } + if (is.date(val)) { + return { + timestampValue: val.toISOString() + }; + } + if (is.array(val)) { + var encodedElements = []; + for (var i = 0; i < val.length; ++i) { + var enc = encodeHelper(val[i]); + if (enc) { + encodedElements.push(enc); + } + } + return { + arrayValue: { + values: encodedElements + } + }; + } + if (is.nil(val)) { + return { + nullValue: 'NULL_VALUE' + }; + } + if (is.instanceof(val, Buffer) || is.instanceof(val, Uint8Array)) { + return { + bytesValue: val + }; + } + if (isPlainObject(val)) { + return { + mapValue: { + fields: encodeFirestoreValue(val) + } + }; + } + throw new Error( + 'Cannot encode ' + val + 'to a Firestore Value.' + + ' The emulator does not yet support Firestore document reference values or geo points.' + ); + }; + + return _.mapValues(data, encodeHelper); +}; + +module.exports = encodeFirestoreValue; diff --git a/lib/localFunction.js b/lib/localFunction.js index 90ec12e0..e7b19570 100644 --- a/lib/localFunction.js +++ b/lib/localFunction.js @@ -3,6 +3,8 @@ var _ = require('lodash'); var request = require('request'); +var encodeFirestoreValue = require('./firestore/encodeFirestoreValue'); + var LocalFunction = function(trigger, urls, controller) { this.name = trigger.name; this.eventTrigger = trigger.eventTrigger; @@ -20,7 +22,11 @@ var LocalFunction = function(trigger, urls, controller) { }; LocalFunction.prototype._isDatabaseFunc = function(eventTrigger) { - return /firebase.database/.test(eventTrigger.eventType); + return _.includes(eventTrigger.eventType, 'firebase.database'); +}; + +LocalFunction.prototype._isFirestoreFunc = function(eventTrigger) { + return _.includes(eventTrigger.eventType, 'cloud.firestore'); }; LocalFunction.prototype._substituteParams = function(resource, params) { @@ -42,26 +48,63 @@ LocalFunction.prototype._requestCallBack = function(err, response, body) { LocalFunction.prototype._call = function(data, opts) { opts = opts || {}; + var operationType; + var dataPayload; + + var makeFirestoreValue = function(input) { + var currentTime = (new Date()).toISOString(); + return { + fields: encodeFirestoreValue(input), + createTime: currentTime, + updateTime: currentTime + }; + }; + if (this.httpsTrigger) { this.controller.call(this.name, data || {}); } else if (this.eventTrigger) { if (this._isDatabaseFunc(this.eventTrigger)) { - var operationType = _.last(this.eventTrigger.eventType.split('.')); - var dataPayload; - if (operationType === 'create') { + operationType = _.last(this.eventTrigger.eventType.split('.')); + switch (operationType) { + case 'create': dataPayload = { data: null, delta: data }; - } else if (operationType === 'update' || operationType === 'write') { + break; + case 'delete': + dataPayload = { + data: data, + delta: null + }; + break; + default: // 'update' or 'write' dataPayload = { data: data.before, delta: data.after }; - } else if (operationType === 'delete') { + } + opts.resource = this._substituteParams(this.eventTrigger.resource, opts.params); + this.controller.call(this.name, dataPayload, opts); + } else if (this._isFirestoreFunc(this.eventTrigger)) { + operationType = _.last(this.eventTrigger.eventType.split('.')); + switch (operationType) { + case 'create': dataPayload = { - data: data, - delta: null + value: makeFirestoreValue(data), + oldValue: {} + }; + break; + case 'delete': + dataPayload = { + value: {}, + oldValue: makeFirestoreValue(data) + }; + break; + default: // 'update' or 'write' + dataPayload = { + value: makeFirestoreValue(data.after), + oldValue: makeFirestoreValue(data.before) }; } opts.resource = this._substituteParams(this.eventTrigger.resource, opts.params); diff --git a/package.json b/package.json index 440dec27..24c04f79 100644 --- a/package.json +++ b/package.json @@ -62,6 +62,7 @@ "glob": "^7.1.2", "google-auto-auth": "^0.7.2", "inquirer": "^0.12.0", + "is": "^3.2.1", "jsonschema": "^1.0.2", "jsonwebtoken": "^7.4.1", "lodash": "^4.6.1",