<|start_filename|>webpack.config.js<|end_filename|>
const path = require('path');
const webpack = require('webpack');
const CleanWebpackPlugin = require('clean-webpack-plugin');
const VueLoaderPlugin = require('vue-loader/lib/plugin');

module.exports = {
  mode: 'development',

  entry: {
    'jsdiff-panel': './src/js/jsdiff-panel.js'
  },

  output: {
    filename: '[name].js',
    path: path.resolve(__dirname, 'src/js/bundle')
  },

  resolve: {
    modules: [
      path.resolve(__dirname, 'src/js'),
      'node_modules'
    ],

    alias: {}
  },

  plugins: [
    new webpack.IgnorePlugin({
      resourceRegExp: /^\.\/locale$/,
      contextRegExp: /moment$/
    }),
    new CleanWebpackPlugin(['src/js/bundle']),
    new VueLoaderPlugin()
  ],

  module: {
    rules: [
      {
        test: /\.vue$/,
        loader: 'vue-loader'
      },
      {
        test: /\.(scss|css)$/,
        use: [
          'style-loader',
          'css-loader',
          'sass-loader'
        ]
      }
    ]
  },

  optimization : {
    runtimeChunk : false,
  },

  // devtool: 'source-map'
  devtool: false
};


<|start_filename|>manifest.json<|end_filename|>
{
  "name": "console.diff(...)",
  "description": "Deep compare complex in-memory objects inside browser devtools panel with console.diff command.",
  "version": "2.0",
  "manifest_version": 2,
  "minimum_chrome_version": "64.0",
  "devtools_page": "src/jsdiff-devtools.html",
  "icons": {
    "28": "src/img/panel-icon28.png",
    "64": "src/img/panel-icon64.png",
    "128": "src/img/panel-icon128.png"
  },
  "background": {
    "persistent": false,
    "scripts": [
      "src/js/jsdiff-background.js"
    ]
  },
  "content_security_policy": "script-src 'self'; object-src 'self'",
  "permissions": [
    "http://*/*",
    "https://*/*",
    "file:///*",
    "clipboardWrite"
  ]
}


<|start_filename|>src/js/jsdiff-devtools.js<|end_filename|>
// resistance is futile

// Create panel
chrome.devtools.panels.create(
    'JSDiff',
    '/src/img/panel-icon28.png',
    '/src/jsdiff-panel.html',
    (panel) => {
      //panel.onSearch.addListener(sendMessage('jsdiff-panel-search'));
      //panel.onShown.addListener(sendMessage('jsdiff-panel-shown'));
      //panel.onHidden.addListener(sendMessage('jsdiff-panel-hidden'));
    }
);

// Create a connection to the background page
if (chrome.devtools.inspectedWindow.tabId !== null) {
  // tabId may be null if user opened the devtools of the devtools
  const backgroundPageConnection = chrome.runtime.connect({name: 'jsdiff-devtools'});
  backgroundPageConnection.postMessage({
    name: 'init',
    tabId: chrome.devtools.inspectedWindow.tabId
  });

  injectScripts();
}

// listen on tabs page reload
chrome.tabs.onUpdated.addListener((tabId, changeInfo, tab) => {
  if ('complete' === changeInfo.status) {
    injectScripts();
  }
});

// Inject console api and messaging proxy
// us shown at: https://developer.chrome.com/extensions/devtools#content-script-to-devtools
function injectScripts() {
  chrome.devtools.inspectedWindow.eval(
      `;(${jsdiff_devtools_extension_api.toString()})();`, {
        useContentScriptContext: false // default: false
      }, (res, error) => {
        if (res && !error) {
          // injected
          chrome.tabs.executeScript(chrome.devtools.inspectedWindow.tabId, {
            file: '/src/js/jsdiff-proxy.js'
          });
        }
      });
}

function jsdiff_devtools_extension_api() {
  if (typeof(console.diff) === 'function') {
    /*already injected*/
    return false;
  }

  function postDataAdapter(set, key, value) {
    try {
      if (
        value instanceof Element ||
        value instanceof Document
      ) {
        return undefined;
      } else if (typeof(value) === 'function') {
        return value.toString();
      } else if (
        value instanceof Object ||
        typeof(value) === 'object'
      ) {
        if (set.has(value)) {
          return undefined;
        }
        set.add(value);
      }

      return value;
    } catch (ignore) {
      return undefined;
    }
  }

  function post(payload) {
    try {
      ['push', 'left', 'right'].forEach((key) => {
        if (payload.hasOwnProperty(key)) {
          let set = new Set();
          payload[key] = JSON.parse(
            JSON.stringify(
              payload[key],
              postDataAdapter.bind(null, set)
            )
          );
          set.clear();
          set = null;
        }
      });
      window.postMessage({payload, source: 'jsdiff-devtools-extension-api'}, '*');
    } catch (e) {
      console.error('%cJSDiff', `
        font-weight: 700;
        color: #000;
        background-color: yellow;
        padding: 2px 4px;
        border: 1px solid #bbb;
        border-radius: 4px;
      `, e);
    }
  }

  Object.assign(console, {
    diff(left, right) {
      post(right === undefined? {push: left} : {left, right});
    },

    diffLeft(left) {
      post({left});
    },

    diffRight(right) {
      post({right});
    },

    diffPush(push) {
      post({push});
    },
  });

  console.log(
    '%cJSDiff', `
      font-weight: 700;
      color: #000;
      background-color: yellow;
      padding: 2px 4px;
      border: 1px solid #bbb;
      border-radius: 4px;
    `,
    `console.diff(left, right); console.diffLeft(left); console.diffRight(right); console.diffPush(next);`
  );

  return true;
}

function sendMessage(message) {
  return function() {
    chrome.runtime.sendMessage(message);
  };
}


<|start_filename|>src/js/jsdiff-panel.js<|end_filename|>
import App from './view/panel.vue';
const app = new App({el: '#app'});


<|start_filename|>src/js/jsdiff-proxy.js<|end_filename|>
;(function(w, x) {
  'use strict';
  w.addEventListener('message', function jsdiff_devtools_extension_proxy(e) {
    if (// message from the same frame
        e.source === w &&
        e.data && typeof(e.data) === 'object' &&
        e.data.source === 'jsdiff-devtools-extension-api'
    ) {
      x && x.sendMessage(e.data);
    }
  });
})(window, chrome.runtime);


<|start_filename|>package.json<|end_filename|>
{
  "name": "jsdiff",
  "version": "2.0.0",
  "description": "![jsdiff](./src/img/panel-icon64.png) --- Chrome devtools extension intended to display result of in-memory object comparisons with the help of dedicated commands invoked via console.",
  "private": true,
  "directories": {
    "doc": "doc"
  },
  "scripts": {
    "test": "echo \"no test\" && exit 1",
    "dev": "webpack --progress --watch",
    "prod": "webpack --progress -p"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/zendive/jsdiff.git"
  },
  "keywords": [
    "devtools",
    "chrome",
    "extension"
  ],
  "author": "<NAME>",
  "license": "proprietary",
  "bugs": {
    "url": "https://github.com/zendive/jsdiff/issues"
  },
  "homepage": "https://github.com/zendive/jsdiff#readme",
  "devDependencies": {
    "clean-webpack-plugin": "~1.0.1",
    "css-loader": "~2.1.0",
    "file-loader": "^3.0.1",
    "node-sass": "~4.14.1",
    "sass-loader": "~7.1.0",
    "style-loader": "~0.23.1",
    "vue-loader": "~15.6.2",
    "vue-template-compiler": "~2.6.6",
    "webpack": "~4.29.0",
    "webpack-cli": "~3.2.1"
  },
  "dependencies": {
    "jsondiffpatch": "~0.4.1",
    "moment": "~2.29.0",
    "vue": "~2.6.12"
  }
}


<|start_filename|>src/js/jsdiff-background.js<|end_filename|>
console.log('jsdiff-background loaded');

const connections = new Map();
let lastApiReq = null;

chrome.runtime.onConnect.addListener((port) => {
  console.log('jsdiff-background connected with, port:', port);

  const devToolsListener = (message, sender, sendResponse) => {
    if (message.name === 'init') {
      const connection = new Connection(port);
      connections.set(message.tabId, connection);
      console.log('jsdiff-background initialized, message:', message);
    }
  };

  port.onMessage.addListener(devToolsListener);
  port.onDisconnect.addListener((port) => {
    port.onMessage.removeListener(devToolsListener);

    for (let [tabId, con] of connections) {
      if (port === con.port) {
        con.onDisconnect();
        connections.delete(tabId);
        break
      }
    }
  });
});

chrome.runtime.onMessage.addListener((req, sender, sendResponse) => {
  const tabId = sender.tab && sender.tab.id;

  if ('jsdiff-devtools-extension-api' === req.source && connections.has(tabId)) {
    lastApiReq = req;
    connections.get(tabId).onApiMessage(req);
  }
  else if ('jsdiff-devtools-panel-shown' === req.type) {
    // message from one of devtool panels
    sendResponse(lastApiReq);
  }

  return '!!magic';
});

class Connection {
  constructor(port) {
    this.port = port;
  }

  /**
   * from content-script jsdiff-proxy.js
   * relay to jsdiff-panel.js
   */
  onApiMessage(req) {
    console.log('jsdiff-background message relay', req);
    if (this.port !== null) {
      this.port.postMessage(req);
    }
  }

  onDisconnect() {
    this.port = null;
  }

}
