<|start_filename|>public/extension/content/contentInject.js<|end_filename|>
const script = chrome.extension.getURL('injected/injected.js');

const s = document.createElement('script');
s.type = 'text/javascript';
s.src = script;

// eslint-disable-next-line func-names
s.onload = function() {
  this.parentNode.removeChild(this);
};
(document.head || document.documentElement).appendChild(s);


<|start_filename|>public/extension/content/contentCommunicate.js<|end_filename|>
// listening to messages from `xstate` package
window.addEventListener('message', msg => {
  const { type } = msg.data;
  switch (type) {
    case 'connect': {
      chrome.runtime.sendMessage(msg.data);
      return;
    }
    case 'update': {
      chrome.runtime.sendMessage(msg.data);
      return;
    }
    case 'disconnect': {
      chrome.runtime.sendMessage(msg.data);
      return;
    }
  }
});


<|start_filename|>src/App.js<|end_filename|>
import React from 'react';
import { StateChart } from './index';
import styled from 'styled-components';
import queryString from 'query-string';
import { LayoutButton } from './LayoutButton';

import AppContext from './AppContext';

const StyledApp = styled.main`
  --color-app-background: #fff;
  --color-border: #c9c9c9;
  --color-primary: rgba(87, 176, 234, 1);
  --color-primary-faded: rgba(87, 176, 234, 0.5);
  --color-primary-shadow: rgba(87, 176, 234, 0.1);
  --color-link: rgba(87, 176, 234, 1);
  --color-disabled: #b3b3b3;
  --color-edge: #c9c9c9;
  --color-edge-active: var(--color-primary);
  --color-secondary: rgba(255, 152, 0, 1);
  --color-secondary-light: rgba(255, 152, 0, 0.5);
  --color-sidebar: #272722;
  --color-gray: #555;
  --color-failure: #ee7170;
  --color-success: #31ae00;
  --radius: 0.2rem;
  --border-width: 2px;
  --sidebar-width: 25rem;
  --shadow: 0 0.5rem 1rem var(--shadow-color, rgba(0, 0, 0, 0.2));
  --duration: 0.2s;
  --easing: cubic-bezier(0.5, 0, 0.5, 1);

  height: 100%;
  overflow: auto;
`;

const query = queryString.parse(window.location.search);

function layoutReducer(state, event) {
  switch (state) {
    case 'full':
      switch (event) {
        case 'TOGGLE':
          return 'viz';
        default:
          return state;
      }
    case 'viz':
      switch (event) {
        case 'TOGGLE':
          return 'full';
        default:
          return state;
      }
    default:
      return state;
  }
}

export function App({ machine, state, serviceSummary, events }) {
  const [layout, dispatchLayout] = React.useReducer(
    layoutReducer,
    query.layout || (!!query.embed ? 'viz' : 'full')
  );

  return (
    <StyledApp data-layout={layout} data-embed={query.embed}>
      <AppContext.Provider value={{ machine: machine, state: state }}>
        <StateChart
          machine={machine}
          state={state}
          serviceSummary={serviceSummary}
          events={events}
        />
        <LayoutButton onClick={() => dispatchLayout('TOGGLE')}>
          {{ full: 'Hide', viz: 'More' }[layout] || 'Show'}
        </LayoutButton>
      </AppContext.Provider>
    </StyledApp>
  );
}


<|start_filename|>public/extension-consumer/src/App.js<|end_filename|>
//@ts-check
import React from 'react';
import './App.css';
import { Machine, assign } from 'xstate';
import { useMachine } from '@xstate/react';

const machine = Machine({
  id: 'fetch',
  initial: 'idle',
  context: {
    retries: 0
  },
  states: {
    idle: {
      on: {
        FETCH: 'loading'
      }
    },
    loading: {
      on: {
        RESOLVE: 'success',
        REJECT: 'failure'
      }
    },
    success: {
      type: 'final'
    },
    failure: {
      on: {
        RETRY: {
          target: 'loading',
          actions: assign({
            retries: (context, event) => context.retries + 1
          })
        }
      }
    }
  }
});

function App() {
  const [state, send] = useMachine(machine, {
    devTools: true
  });
  console.log('App State:', state);
  console.log('App State string:', JSON.stringify(state));

  if (state.matches('idle')) {
    return (
      <div>
        <h2>Idle</h2>
        <button onClick={() => send('FETCH')}>FETCH</button>
      </div>
    );
  } else {
    return <h2>Non-idle state</h2>;
  }
}

export default App;


<|start_filename|>public/src/App.js<|end_filename|>
import './App.css';
import { App as XStateViz } from '@statecharts/xstate-viz';
import React from 'react';
import Dropdown from './Dropdown';
import Logo from './Logo';
import styled from 'styled-components';

const formatLabel = service =>
  `${service.machine.id} - (uid: ${service.serviceId.slice(0, 3)}...)`;

const StyledLogo = styled(Logo)`
  height: 2rem;
`;

const Header = styled.div`
  display: flex;
  flex-direction: row;

  align-items: center;
  padding-top: 2px;
  border-bottom: 1px solid #efefef;
  padding-bottom: 2px;
  padding-left: 1.5rem;
`;

const App = ({ services }) => {
  const [selectedServiceId, setSelectedServiceId] = React.useState(
    services.length > 0 ? services[services.length - 1].serviceId : null
  );

  const selectedService = services.find(
    service => service.serviceId === selectedServiceId
  );

  return (
    <>
      <Header>
        <StyledLogo />
        {selectedService !== undefined ? (
          <Dropdown
            selectedItem={{
              value: selectedServiceId,
              label: `${
                selectedService.hasStopped ? 'Stopped: ' : ''
              }${formatLabel(selectedService)}`
            }}
            setSelectedItem={serviceId => setSelectedServiceId(serviceId)}
            items={services
              .filter(
                service =>
                  (service.hasStopped &&
                    service.serviceId !== selectedServiceId) === false
              )
              .map(service => ({
                label: formatLabel(service),
                value: service.serviceId
              }))}
          />
        ) : (
          <h2>No service</h2>
        )}
      </Header>
      {services.length > 0 ? (
        selectedService.hasStopped ? (
          <h2>The selected service has been stopped.</h2>
        ) : (
          <XStateViz
            machine={selectedService.machine}
            state={selectedService.state}
            serviceSummary={formatLabel(selectedService)}
            events={selectedService.events}
          />
        )
      ) : (
        <h2>No services to display</h2>
      )}
    </>
  );
};

export default App;


<|start_filename|>public/extension/devtools/devtools.js<|end_filename|>
chrome.devtools.panels.create(
  'XState DevTools',
  null,
  'devtools/build/index.html',
  () => {},
);


<|start_filename|>public/src/Logo.js<|end_filename|>
import React from 'react';

const Logo = ({ className }) => (
  <svg
    version="1.1"
    xmlnsXlink="http://www.w3.org/1999/xlink"
    x="0px"
    y="0px"
    viewBox="0 0 279.8 98.5"
    xmlSpace="preserve"
    className={className}
  >
    <g>
      <g>
        <path
          d="M55.8,10.4L55.8,10.4h19.5L55.6,44.6l0,0C49.6,33.9,49.7,20.9,55.8,10.4z"
          fill="var(--fill, black)"
        />
        <polygon
          points="75.4,88.1 30.5,10.4 11,10.4 33.4,49.3 11,88.1 30.5,88.1 43.2,66.2 55.8,88.1"
          fill="var(--fill, black)"
        />
      </g>
      <g className="st0">
        <path
          d="M89.1,59.8c3.2,2.7,6.9,4,11.3,4.1c5.2-0.1,7.8-2,7.9-5.7c0-3.1-1.7-4.9-5.2-5.5c-1.6-0.2-3.4-0.5-5.3-0.8
			c-3.4-0.6-6-1.9-7.8-3.9c-1.8-2.1-2.8-4.6-2.8-7.6c0-3.6,1.2-6.5,3.5-8.7c2.2-2.2,5.4-3.3,9.5-3.3c4.9,0.1,9.2,1.5,12.9,4.2
			l-3.4,5.1c-3-2-6.3-3.1-9.8-3.2c-1.9,0-3.4,0.5-4.6,1.4c-1.3,1-2,2.5-2,4.4c0,1.2,0.4,2.3,1.3,3.2c0.9,1,2.3,1.7,4.3,2
			c1.1,0.2,2.6,0.4,4.5,0.6c3.7,0.5,6.5,1.9,8.3,4.1c1.8,2.2,2.6,4.7,2.6,7.7C114,66,109.4,69.9,100.3,70c-5.9,0-10.9-1.9-15.2-5.6
			L89.1,59.8z"
          fill="var(--fill, black)"
        />
        <path
          d="M135.5,34.2h-10.9v-5.5h27.8v5.5h-10.9v35.5h-6V34.2z"
          fill="var(--fill, black)"
        />
        <path
          d="M180.9,60.5h-15.1l-3.1,9.2h-6.4l14.4-41h5l14.4,41H184L180.9,60.5z M179.2,55l-5.7-17.9h-0.1L167.6,55H179.2z"
          fill="var(--fill, black)"
        />
        <path
          d="M205.4,34.2h-10.9v-5.5h27.8v5.5h-10.9v35.5h-6V34.2z"
          fill="var(--fill, black)"
        />
        <path
          d="M234.2,28.7h25.4v5.8h-19.4v11.7h16.5v5.5h-16.5v12.1h19.4v5.8h-25.4V28.7z"
          fill="var(--fill, black)"
        />
      </g>
    </g>
  </svg>
);

export default Logo;


<|start_filename|>public/moveBuild.js<|end_filename|>
const path = require('path');
const fs = require('fs');
const rimraf = require('rimraf');

const craBuildDir = path.join(__dirname, 'build');
const targetBuildDir = path.join(__dirname, 'extension', 'devtools', 'build');

rimraf(targetBuildDir, () => {
  console.log('Cleaned previous target build directory');
  fs.rename(craBuildDir, targetBuildDir, err => {
    if (err) {
      console.log(
        'Received error before managed to move build dir to target destination:',
        err
      );
    } else {
      console.log(`Moved build to ${targetBuildDir}`);
    }
  });
});


<|start_filename|>public/extension/injected/injected.js<|end_filename|>
/* Source: https://stackoverflow.com/a/2117523 */
function uuidv4() {
  return ([1e7] + -1e3 + -4e3 + -8e3 + -1e11).replace(/[018]/g, c =>
    (
      c ^
      (crypto.getRandomValues(new Uint8Array(1))[0] & (15 >> (c / 4)))
    ).toString(16)
  );
}

const uselessInterface = {
  send: () => {},
  disconnect: () => {},
  init: () => {}
};

const __XSTATE_DEVTOOLS_EXTENSION__ = {
  connect: (_, machine) => {
    if (machine) {
      try {
        const serviceId = uuidv4();
        window.postMessage({
          type: 'connect',
          payload: {
            serviceId: serviceId,
            machine: JSON.stringify(machine.config),
            state: JSON.stringify(machine.initialState)
          }
        });

        return {
          send: (event, state) => {
            const formattedEvent = {
              event: event,
              time: Date.now()
            };
            window.postMessage({
              type: 'update',
              payload: {
                serviceId: serviceId,
                state: JSON.stringify(state),
                event: JSON.stringify(formattedEvent)
              }
            });
          },
          disconnect: () => {
            window.postMessage({
              type: 'disconnect',
              payload: {
                serviceId: serviceId
              }
            });
          },
          init: () => {}
        };
      } catch (error) {
        console.warn(
          `XState DevTools browser extension: Caught error. 
          Name: ${error.name}
          Message: ${error.message}
          Stack: ${error.stack}
          `
        );
        return uselessInterface;
      }
    } else {
      console.warn(
        "XState DevTools browser extension: This application doesn't appear to be using a compatible XState package version. Please install XState version 4.7.0+"
      );
      return uselessInterface;
    }
  }
};

window.__REDUX_DEVTOOLS_EXTENSION__ = __XSTATE_DEVTOOLS_EXTENSION__;


<|start_filename|>public/src/Dropdown.js<|end_filename|>
import React from 'react';
import Select from 'react-select';
import PropTypes from 'prop-types';

const Dropdown = ({ items, selectedItem, setSelectedItem }) => {
  return (
    <Select
      className="react-select-container"
      classNamePrefix="react-select"
      value={selectedItem}
      onChange={({ value }) => setSelectedItem(String(value))}
      options={items}
      menuPlacement="top"
      styles={{
        container: base => ({
          ...base,
          width: '50%',
          margin: '0 auto',
          border: '1px solid grey'
        }),
        menu: base => ({
          ...base,
          zIndex: 100
        })
      }}
    />
  );
};

Dropdown.propTypes = {
  selectedItem: PropTypes.any,
  items: PropTypes.any,
  setSelectedItem: PropTypes.any
};

export default Dropdown;


<|start_filename|>public/extension/manifest.json<|end_filename|>
{
  "name": "XState DevTools",
  "author": "<NAME>",
  "version": "0.5.3",
  "devtools_page": "devtools/devtools.html",
  "description": "A Chrome extension for inspecting XState machines running in your app.",
  "manifest_version": 2,
  "content_security_policy": "script-src 'self' 'unsafe-eval' ; object-src 'self'",
  "background": {
    "scripts": ["hot-reload.js", "background.js"],
    "persistent": false
  },
  "icons": {
    "16": "assets/16x16.png",
    "48": "assets/48x48.png",
    "128": "assets/128x128.png"
  },
  "content_scripts": [
    {
      "matches": ["<all_urls>"],
      "js": ["content/contentCommunicate.js", "content/contentInject.js"],
      "run_at": "document_start"
    }
  ],
  "web_accessible_resources": ["injected/injected.js"],
  "permissions": ["tabs", "<all_urls>"]
}
