Friday, 20 August 2021

vuex shared state in chrome extension

I have a chrome extension with the following webpack.config.js:

module.exports = {
  mode,
  entry: {
    "content/content": [
      "./src/js/content/content.js",
      "./src/js/store.js",
      "./src/js/content/overlay/style.scss",
    ],
    "background/background": [
      "./src/js/background/utils.js",
      "./src/js/background/background.js",
    ],
    "overlay/overlay": "./src/js/content/overlay/index.js",
    "popup/popup": "./src/js/content/popup/index.js",
  },

looking at

Shared vuex state in a web-extension (dead object issues)

https://github.com/xanf/vuex-shared-mutations

Adding a wrapper around browser local storage:

browserStore.js

import browser from "@/js/browser";

export function getStorageValue(payload) {
  return new Promise((resolve) => {
    browser.storage.local.get(payload, (items) => {
      if (items) {
        resolve(items);
      }
    });
  });
}

export function setStorageValue(payload) {
  return new Promise((resolve) => {
    browser.storage.local.set(payload, (value) => {
      resolve(value);
    });
  });
}

In "./src/js/content/popup/firstpage/store/index.js" vuex store is defined as:

import Vue from "vue";
import Vuex from "vuex";
import "es6-promise/auto";
import createMutationsSharer from "vuex-shared-mutations";

import dummyData from "./dummyData";

import { getStorageValue, setStorageValue } from "@/js/store";

Vue.use(Vuex);

export default new Vuex.Store({
  state: {
    chromePagesState: {
      allSections: [],
    },
  },
  getters: {
    ...
  },
  mutations: {
    setChromePagesState(state, value) {
        ...
    },
    // this function is to be called from a content script
    addWhiteListedItem(state, item) {
      // state not initialized here
      state.chromePagesState.allSections[0].itemSectionCategory[0].tasks.splice(
        0,
        0,
        item
      );
    },
    ...
  }
  actions: {
    async saveChromePagesState({ state }) {
      // Save only needed fields
      let data = {
        ...
      };
      await setStorageValue({ inventoryData: JSON.stringify(data) });
    },
    async loadChromePagesState({ commit }) {
      const json = await getStorageValue("inventoryData");
      // json always an empty object 
      commit(
        "setChromePagesState",
        Object.keys(json).length === 0 && json.constructor === Object
          ? json
          : dummyData
      );
    },
    async loadChromePagesStateBrowser({ commit }) {
      browser.runtime
        .sendMessage({ type: "storeinit", key: "chromePagesState" })
        .then(async (chromePagesState) => {
          const json = await getStorageValue("inventoryData");
          commit(
            "setChromePagesState",
            Object.keys(json).length === 0 && json.constructor === Object
              ? json
              : dummyData
          );
        });
    },
    plugins: [
        createMutationsSharer({
          predicate: [
            "addWhiteListedItem",
            "loadChromePagesState",
            "loadChromePagesStateBrowser",
          ],
        }),
    ],
  },

the background script has a listener; src/background/background.js:

browser.runtime.onMessage.addListener((message, sender) => {
  if (message.type === "storeinit") {
    return Promise.resolve(store.state[message.key]);
  }
});

The content script that needs to make use of the shared store has an entry point in content.js:

import { initOverlay } from '@/js/content/overlay';
import browser from '@/js/browser';

browser.runtime.onMessage.addListener(function (request, _sender, _callback) {
  // vue component gets created here:
  if (request && request.action === 'show_overlay') {
    initOverlay();
  }

  return true; // async response
});

initOverlay() creates a vue component in ./src/js/content/overlay/index.js:

import Vue from "vue";
import Overlay from "@/js/content/overlay/Overlay.vue";
import browser from "@/js/browser";
import { getStorageValue } from "@/js/store";

import store from "../popup/firstpage/store";

Vue.prototype.$browser = browser;

export async function initOverlay(lockScreen = defaultScreen, isPopUp = false) {
    ...
    setVueOverlay(overlayContainer, cover);
    ...
}

  function setVueOverlay(overlayContainer, elem) {
    if (!elem.querySelector("button")) {
      elem.appendChild(overlayContainer);
      elem.classList.add("locked");

      new Vue({
        el: overlayContainer,
        store,
        render: (h) => h(Overlay, { props: { isPopUp: isPopUp } }),
      });
    }
  }

Overlay.vue only needs to call a mutation (addWhiteListedItem) from store:

<template>
              <button
                @click="addToWhiteList()"
                >White list!</button
              >
</template>

<script>
import { mapState, mapMutations } from "vuex";

export default {

  data() {
    return {
    };
  },
  computed: mapState(["chromePagesState"]),
  methods: {
    ...mapMutations(["addWhiteListedItem"]),
    addToWhiteList() {
      console.log("addToWhiteList()");

        let newItem = {
           ... 
        };
        // store not defined fails with:
        Uncaught TypeError: Cannot read property 'itemSectionCategory' of undefined
        at Store.addWhiteListedItem (index.js:79)
        at wrappedMutationHandler (vuex.esm.js:853)
        at commitIterator (vuex.esm.js:475)
        at Array.forEach (<anonymous>)
        at eval (vuex.esm.js:474)
        at Store._withCommit (vuex.esm.js:633)
        at Store.commit (vuex.esm.js:473)
        at Store.boundCommit [as commit] (vuex.esm.js:418)
        at VueComponent.mappedMutation (vuex.esm.js:1004)
        at eval (Overlay.vue?./node_modules/vue-loader/lib??vue-loader-options:95)
        this.addWhiteListedItem(newItem);

      }, 1500);
    },
  },
};
</script>

Why doesn't Overlay.vue "see" the state of store?



from vuex shared state in chrome extension

No comments:

Post a Comment