UNPKG

react-chrome-redux

Version:

A set of utilities for building Redux applications in Google Chrome Extensions.

250 lines (198 loc) 8.73 kB
'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); var _lodash = require('lodash.assignin'); var _lodash2 = _interopRequireDefault(_lodash); var _constants = require('../constants'); var _serialization = require('../serialization'); var _patch = require('../strategies/shallowDiff/patch'); var _patch2 = _interopRequireDefault(_patch); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } var backgroundErrPrefix = '\nLooks like there is an error in the background page. ' + 'You might want to inspect your background page for more details.\n'; var Store = function () { /** * Creates a new Proxy store * @param {object} options An object of form {portName, state, extensionId, serializer, deserializer, diffStrategy}, where `portName` is a required string and defines the name of the port for state transition changes, `state` is the initial state of this store (default `{}`) `extensionId` is the extension id as defined by chrome when extension is loaded (default `''`), `serializer` is a function to serialize outgoing message payloads (default is passthrough), `deserializer` is a function to deserialize incoming message payloads (default is passthrough), and patchStrategy is one of the included patching strategies (default is shallow diff) or a custom patching function. */ function Store(_ref) { var _this = this; var portName = _ref.portName, _ref$state = _ref.state, state = _ref$state === undefined ? {} : _ref$state, _ref$extensionId = _ref.extensionId, extensionId = _ref$extensionId === undefined ? null : _ref$extensionId, _ref$serializer = _ref.serializer, serializer = _ref$serializer === undefined ? _serialization.noop : _ref$serializer, _ref$deserializer = _ref.deserializer, deserializer = _ref$deserializer === undefined ? _serialization.noop : _ref$deserializer, _ref$patchStrategy = _ref.patchStrategy, patchStrategy = _ref$patchStrategy === undefined ? _patch2.default : _ref$patchStrategy; _classCallCheck(this, Store); if (!portName) { throw new Error('portName is required in options'); } if (typeof serializer !== 'function') { throw new Error('serializer must be a function'); } if (typeof deserializer !== 'function') { throw new Error('deserializer must be a function'); } if (typeof patchStrategy !== 'function') { throw new Error('patchStrategy must be one of the included patching strategies or a custom patching function'); } this.portName = portName; this.readyResolved = false; this.readyPromise = new Promise(function (resolve) { return _this.readyResolve = resolve; }); this.extensionId = extensionId; // keep the extensionId as an instance variable this.port = chrome.runtime.connect(this.extensionId, { name: portName }); this.safetyHandler = this.safetyHandler.bind(this); this.safetyMessage = chrome.runtime.onMessage.addListener(this.safetyHandler); this.serializedPortListener = (0, _serialization.withDeserializer)(deserializer)(function () { var _port$onMessage; return (_port$onMessage = _this.port.onMessage).addListener.apply(_port$onMessage, arguments); }); this.serializedMessageSender = (0, _serialization.withSerializer)(serializer)(function () { var _chrome$runtime; return (_chrome$runtime = chrome.runtime).sendMessage.apply(_chrome$runtime, arguments); }, 1); this.listeners = []; this.state = state; this.patchStrategy = patchStrategy; // Don't use shouldDeserialize here, since no one else should be using this port this.serializedPortListener(function (message) { switch (message.type) { case _constants.STATE_TYPE: _this.replaceState(message.payload); if (!_this.readyResolved) { _this.readyResolved = true; _this.readyResolve(); } break; case _constants.PATCH_STATE_TYPE: _this.patchState(message.payload); break; default: // do nothing } }); this.dispatch = this.dispatch.bind(this); // add this context to dispatch } /** * Returns a promise that resolves when the store is ready. Optionally a callback may be passed in instead. * @param [function] callback An optional callback that may be passed in and will fire when the store is ready. * @return {object} promise A promise that resolves when the store has established a connection with the background page. */ _createClass(Store, [{ key: 'ready', value: function ready() { var cb = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : null; if (cb !== null) { return this.readyPromise.then(cb); } return this.readyPromise; } /** * Subscribes a listener function for all state changes * @param {function} listener A listener function to be called when store state changes * @return {function} An unsubscribe function which can be called to remove the listener from state updates */ }, { key: 'subscribe', value: function subscribe(listener) { var _this2 = this; this.listeners.push(listener); return function () { _this2.listeners = _this2.listeners.filter(function (l) { return l !== listener; }); }; } /** * Replaces the state for only the keys in the updated state. Notifies all listeners of state change. * @param {object} state the new (partial) redux state */ }, { key: 'patchState', value: function patchState(difference) { this.state = this.patchStrategy(this.state, difference); this.listeners.forEach(function (l) { return l(); }); } /** * Replace the current state with a new state. Notifies all listeners of state change. * @param {object} state The new state for the store */ }, { key: 'replaceState', value: function replaceState(state) { this.state = state; this.listeners.forEach(function (l) { return l(); }); } /** * Get the current state of the store * @return {object} the current store state */ }, { key: 'getState', value: function getState() { return this.state; } /** * Stub function to stay consistent with Redux Store API. No-op. */ }, { key: 'replaceReducer', value: function replaceReducer() { return; } /** * Dispatch an action to the background using messaging passing * @param {object} data The action data to dispatch * @return {Promise} Promise that will resolve/reject based on the action response from the background */ }, { key: 'dispatch', value: function dispatch(data) { var _this3 = this; return new Promise(function (resolve, reject) { _this3.serializedMessageSender(_this3.extensionId, { type: _constants.DISPATCH_TYPE, portName: _this3.portName, payload: data }, null, function (resp) { var error = resp.error, value = resp.value; if (error) { var bgErr = new Error('' + backgroundErrPrefix + error); reject((0, _lodash2.default)(bgErr, error)); } else { resolve(value && value.payload); } }); }); } }, { key: 'safetyHandler', value: function safetyHandler(message) { if (message.action === 'storeReady') { // Remove Saftey Listener chrome.runtime.onMessage.removeListener(this.safetyHandler); // Resolve if readyPromise has not been resolved. if (!this.readyResolved) { this.readyResolved = true; this.readyResolve(); } } } }]); return Store; }(); exports.default = Store;