UNPKG

evrythng

Version:

Official Javascript SDK for the EVRYTHNG API.

175 lines (157 loc) 5.27 kB
import Entity from './Entity' import Resource from '../resource/Resource' import settings from '../settings' import symbols from '../symbols' import getCurrentPosition from '../util/getCurrentPosition' import isString from 'lodash-es/isString' import isFunction from 'lodash-es/isFunction' import isUndefined from 'lodash-es/isUndefined' const path = '/actions/:type' /** * Represents an Action entity. * * @extends Entity */ export default class Action extends Entity { /** * Return overridden resource factory for Actions. Actions require an * action type to be specified before the ID. The action creation is also * different from any other Resource, as it fetches the user location and * pre-populates the action payload with the Resource type. * * @static * @return {{action: Function}} */ static resourceFactory () { return { action (actionType, id) { if (!actionType) { throw new TypeError('Action type cannot be empty.') } if (!isString(actionType)) { throw new TypeError('Action type must be a name string') } const relativePath = path.replace(':type', actionType) const context = this // Creates and returns Resource of type Action. // Override property resource create to allow custom value params and // fetch the user's geolocation. See `createAction()`. return Object.assign(Resource.factoryFor(Action, relativePath).call(this, id), { create (...args) { return createAction.call(this, context, actionType, ...args) } }) } } } } /** * Create action of given type. Allow empty body and fetch geolocation if * setup and available. * * @param {Scope|Resource|Entity} caller - Where the resource is attached to * @param {string} actionType - Type of action * @param {*} args - Rest of arguments passed to resource creation * @return {Promise} */ function createAction (caller, actionType, ...args) { let [data, ...rest] = normalizeArguments(...args) const [options] = rest // Auto-fill action payload with resource type and entity id. data = Array.isArray(data) ? data.map((action) => fillAction(action, caller, actionType)) : (data = fillAction(data, caller, actionType)) const baseCreate = Resource.prototype.create.bind(this) const updatedArgs = () => [data, ...rest] if (useGeolocation(options)) { return getCurrentPosition() .then((position) => { data = fillActionLocation(data, position) return baseCreate(...updatedArgs()) }) .catch((err) => { console.info(`Unable to get position: ${err}`) return baseCreate(...updatedArgs()) }) } else { return baseCreate(...updatedArgs()) } } /** * Add an empty action object if none is provided. * * @param {*} args - Arguments array. * @return {Array} - Same input format, with first data param updated. * @example * * product.action().create() * product.action().create(<Action>) */ function normalizeArguments (...args) { const firstArg = args[0] if (isUndefined(firstArg) || isFunction(firstArg)) { args.unshift({}) } return args } /** * Fill action type and entity ID. Resource type takes precedence over given * type. Entity ID overrides any pre-defined target ID, if action is created on * an Entity instance. * * @param {Object} data - Action data * @param {Scope|Resource|Entity} caller - Where the resource is attached to * @param {string} actionType - Resource action type * @return {Object} - New action data */ function fillAction (data, caller, actionType) { const action = Object.assign({}, data) // Fill type from Resource or pre-defined type. action.type = actionType !== 'all' ? actionType : data.type || '' // Fill in entity ID if called on an entity. const entityIdentifier = getIdentifier(caller) if (entityIdentifier) { action[entityIdentifier] = caller.id } return action } /** * Actions can be performed on Products, Thngs and Collections and the * property on the payload differs based on the target. * * @param {Scope|Resource|Entity} caller - Where the resource is attached to * @return {string} */ function getIdentifier (caller) { return caller instanceof Entity ? caller[symbols.actionIdentifier] ? caller[symbols.actionIdentifier] : '' : '' } /** * Check if the library should request the browser geolocation. If local * option is available, it takes precedence over global setting. * * @param {Settings|Function} options - If function, it's the callback * @return {boolean} */ function useGeolocation (options) { return options && !isUndefined(options.geolocation) ? options.geolocation : settings.geolocation } /** * Fill action location with coordinates from browser's Geolocation API. * * @param {Object} data - Action data * @param {Object} position - Geolocation API position coordinates * @return {Object} - New action data */ function fillActionLocation (data, position) { const action = Object.assign({}, data) action.location = { latitude: position.coords.latitude, longitude: position.coords.longitude } action.locationSource = 'sensor' return action }