summaryrefslogtreecommitdiffstats
path: root/browser/components/payments/res/PaymentsStore.js
diff options
context:
space:
mode:
Diffstat (limited to 'browser/components/payments/res/PaymentsStore.js')
-rw-r--r--browser/components/payments/res/PaymentsStore.js97
1 files changed, 97 insertions, 0 deletions
diff --git a/browser/components/payments/res/PaymentsStore.js b/browser/components/payments/res/PaymentsStore.js
new file mode 100644
index 0000000000..7e439076d8
--- /dev/null
+++ b/browser/components/payments/res/PaymentsStore.js
@@ -0,0 +1,97 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * The PaymentsStore class provides lightweight storage with an async publish/subscribe mechanism.
+ * Synchronous state changes are batched to improve application performance and to reduce partial
+ * state propagation.
+ */
+
+export default class PaymentsStore {
+ /**
+ * @param {object} [defaultState = {}] The initial state of the store.
+ */
+ constructor(defaultState = {}) {
+ this._defaultState = Object.assign({}, defaultState);
+ this._state = defaultState;
+ this._nextNotifification = 0;
+ this._subscribers = new Set();
+ }
+
+ /**
+ * Get the current state as a shallow clone with a shallow freeze.
+ * You shouldn't modify any part of the returned state object as that would bypass notifying
+ * subscribers and could lead to subscribers assuming old state.
+ *
+ * @returns {Object} containing the current state
+ */
+ getState() {
+ return Object.freeze(Object.assign({}, this._state));
+ }
+
+ /**
+ * Used for testing to reset to the default state from the constructor.
+ * @returns {Promise} returned by setState.
+ */
+ async reset() {
+ return this.setState(this._defaultState);
+ }
+
+ /**
+ * Augment the current state with the keys of `obj` and asynchronously notify
+ * state subscribers. As a result, multiple synchronous state changes will lead
+ * to a single subscriber notification which leads to better performance and
+ * reduces partial state changes.
+ *
+ * @param {Object} obj The object to augment the state with. Keys in the object
+ * will be shallow copied with Object.assign.
+ *
+ * @example If the state is currently {a:3} then setState({b:"abc"}) will result in a state of
+ * {a:3, b:"abc"}.
+ */
+ async setState(obj) {
+ Object.assign(this._state, obj);
+ let thisChangeNum = ++this._nextNotifification;
+
+ // Let any synchronous setState calls that happen after the current setState call
+ // complete first.
+ // Their effects on the state will be batched up before the callback is actually called below.
+ await Promise.resolve();
+
+ // Don't notify for state changes that are no longer the most recent. We only want to call the
+ // callback once with the latest state.
+ if (thisChangeNum !== this._nextNotifification) {
+ return;
+ }
+
+ for (let subscriber of this._subscribers) {
+ try {
+ subscriber.stateChangeCallback(this.getState());
+ } catch (ex) {
+ console.error(ex);
+ }
+ }
+ }
+
+ /**
+ * Subscribe the object to state changes notifications via a `stateChangeCallback` method.
+ *
+ * @param {Object} component to receive state change callbacks via a `stateChangeCallback` method.
+ * If the component is already subscribed, do nothing.
+ */
+ subscribe(component) {
+ if (this._subscribers.has(component)) {
+ return;
+ }
+
+ this._subscribers.add(component);
+ }
+
+ /**
+ * @param {Object} component to stop receiving state change callbacks.
+ */
+ unsubscribe(component) {
+ this._subscribers.delete(component);
+ }
+}