Code Monkey home page Code Monkey logo

chrome-extension's Issues

Mujo Plugins - Open Sourcing

  • Start Date: 8-20-2019
  • RFC PR: (leave this empty)

In progress... see progress here
https://www.notion.so/jjcblw/e4c79acfc26344278f94148d2b27ac2c?v=a774bf4be0054b5a9466010135e48f23

Summary

This document is going to outline in depth how we are planning to achieve what we are calling Mujō React plugins. Essentially I started out this document on how I planned to open-source this project and the things needed to be able to do that. It was very high level and had many gaps. This document will have a more definitive guide on not only how we can achieve this but also what the end goal is.

Overview

There are a few things that need to be tidied up to be able to achieve this. Mostly building out the API to support this, but the main idea is its all one React component.

import {
  Background,
  Content,
  Setting,
  Tab,
} from '@mujo/plugin/components'
import { useHeartBeat, useMessages } from '@mujo/plugin/hooks'

const MyBackgroundScripts = () => {
  useHeartBeat(() => {
    // triggers on each heart beat
  })
  useMessages(
    message => {
      // trigger when message gets fired
    },
    [MESSAGE_TYPE]
  )
  return null
}

const MyPlugin = () => (
  <>
    <Background>
      <MyBackgroundScripts />
    </Background>
    <Setting {...setting} />
    <Tab {...tab} />
    <Content {...content} />
  </>
)

MyPlugin.manifest = {
  optionalPermissions: ['notifications'],
  contextSecurityPolicy: { connectSrc: 'https://getmujo.com' },
}

export default MyPlugin

Motivation

The project is open-sourced will allow for more meaningful collaboration. It will show the dedication of Mujō to being part of the community of Open Source Software.

Essentially Mujō Paid version will be a fork of the OS version with some added plugins. The plugins will contain the IP of Mujō LLC.

Design Breakdown

Existing Clean Up

Tab View

First and foremost we will need a tabbing system for the main view. We already have a spec for it. There are some changes to this implement that we need to factor in. Essentially we need the ability to push new tabs into the design.

Settings

Settings would need to be more pluggable. I think we should maybe focus on implementing some of the components that would go into the plugin spec. It sort of works that way now but should allow users to start fleshing it out more.

...
<Setting title="Foo" description="Bar" type="boolean" value={foo}/>
...

This should be able to go anywhere.

Port Background Script to React

This should not entail much work. It actually should just hook into the existing functionality, but wrap it in effects.

Set-up for plugins

Config file

Using cosmic config we need to set up some configuration in the root of the directory. This will allow users to have a place to specify plugins. It will largely be based on the manifest file format but having something separate allows us to generate the manifest easier to build time. An example of this file.

// TODO: this is subject to change
const pkg = require('./package.json')
module.exports = {
  plugins: ['mujo-screen-time'],
  longName: `${pkg.name} - Be mindful of your time`,
  offlineEnabled: true,
  permissions: [
    'tabs',
    'webNavigation',
    'alarms',
    'background',
    'topSites',
  ],
  optionalPermissions: ['activeTab', 'https://*/*', 'http://*/*'],
  icons: {
    16: 'favicon.png',
    32: 'favicon.png',
  },
  browserAction: {
    defaultIcon: 'favicon.png',
  },
  webAccessibleResources: ['*'],
  contentSecurityPolicy: {
    scriptSrc: ['self', 'https:getmujo.com'],
    fontSrc: ['https://fonts.gstatic.com'],
    imgSrc: ['self'],
  },
}

Note that the keys are transformed from the camelCase format in this file to snake_case in the generated manifest.

Manifest generator

The manifest generator will use values from the package.json and config to compile a manifest file on the build. It will pull name and version from package.json, any other manifest keys from config, and any additional things from the plugins. This could be optionalPermissions or the additional to the contentSecurityPolicy. After pulling all this info it merges and makes a manifest.json from these values. May be beneficial to have a validator as well but should not be required.

Plugin resolver

The plugins resolver will do a few things on build time.

  • It will read file from a .mujorc file using cosmic config
  • From the plugins array of this config, we are going to build a file that is going to be required by all the scripts.
// example of the generated file.
export default {
  'mujo-screen-time': safeRequire('../plugins/mujo-screen-time'),
  'mujo-predictive-breaks': safeRequire('mujo-predictive-breaks'),
}
  • The plugins can live in a plugins folder locally or from an npm module.

Plugin Interface

Essentially the plugin is a React Component. That is it, the interface to the plugin is all driven by paradigms in React. These interfaces will be in the form of a separate module.

Hooks

Hooks will allow plugins to get access to many of the applications internal API's. Things like setting up some keys in the database or event listening to the internal alarm system. Some of these hooks can run on all of the scripts and will look like from your application that the hooks are global working between the scripts.

Universal Hooks

useStorage

This hook should allow the plugin to access data inside the database. It should allow for access to the database between all the scripts.

const [value, setValue] = useStorage('KEY', defaultValue)
useMessage

This hook is will allow plugins to send and receive messages from NTP and content to background scripts. This interface is unified between all scripts for ease of use.

useMessage((type, body, response) => {
  // do something with message
  // TODO: figure out interface for sending out messages
  // eg. responses, and broadcasting on background or sending a message
  // on client scripts.
})
usePermission

This hook allows plugins to request additional optional permissions. The returned object will have two methods and a boolean flag if the application has permission. With the method, requestPermssion you have the ability to request permissions you asked for. removePermission will remove the permissions from your application.

const {
  hasPermission,
  requestPermission,
  removePermission,
} = usePermission(permissions)

Background Hooks

useHeartBeat

This hook will allow plugins to listen to the heartbeat of the application. Its is a 15-minute tick that the background process has. It is also based on alarms so the timing is not precise at all and sometimes can be deferred by the browser. useHeartBeat should be used for triggers based on usage. This allows the plugin to interject at times of activity. It gets a single argument to the effect function which is if the user is currently active.

useHeartBeat(isActive => {
  if (isActive && otherCondition) return doSomething()
}) // TODO maybe have dependencies here for cache busting

NTP/Content Hooks

useTheme

This is primarily used to tap into the theme of the application. Right now there is dark and light mode. Both modes have a series of colors associated with them.

// This is the dark theme
{
  foreground: 'mischka',
  foregroundSecondary: 'white',
  background: 'outerSpace',
  backgroundSecondary: 'gravel',
  highlight: 'saltBox',
  buttonStyle: 'primary',
}

These are values you may pass into the box component.

...
<Box backgroundColor={background} color={foreground}>I am themed</Box>
<Button design={buttonStyle}>Ok?</Button> // TODO: swap style to design?
...

To use this hook to get the values above.

const { background, foreground, highlight } = useTheme()

Components

Components are a declarative way to layout your plugin. The top-level of you main component should avoid using hooks since the different components in that level will have different render targets and will most likely get out of sync, eg, if you use useState there will be two or possibly three different states in the three different render targets.

const MyPlugin = () => (
  <>
    <Background>
      <MyBackgroundComponent />
    </Background>
    <Setting />
    <Tab>
      <MyTabComponent />
    </Tab>
    <Content>
      <MyContentComponent />
    </Content>
  </>
)
export default MyPlugin

This is how we will allow plugin content to gen into the react render tree. mujo-code/ingress

Adding to the Manifest

Each plugin should have the ability to add in some additional params to the manifest this will allow plugins to get access to new origins, and to add optional permissions. To allow this the plugin should have a static key of "manifest".

MyPlugin = {
  optionalPermissions: ['activeTab'],
  contentSecurityPolicy: { connectSrc: ['https://getmujo.com'] }
}

Right now we should have as limited as a scope as possible only allowing optional permissions and ContentSecourityPolicy.

Background

The Background component will allow you to create a tree for your background functionality in your plugin. This means you will have access to all the background hooks in your application. Children do no get rendered to the DOM, and this render target is more about using hooks to access functionality on the backend.

const MyBackgroundComponent = () => {
  useHeartBeat(() => { ... })
  return null
}

...
<Background><MyBackgroundComponent /></Background>
...

Content

The Content component is a component that very much like the Background component in the sense it allows you to target only the content scripts of the application. The component is rendered inside of a react-modal and will overlay the page the content script is running in.

const MyContentComponent = () => {
  const [isActive] = useStorage('IS_MY_PLUGIN_ACTIVE')
  if (!isActive) return null
  return <Box>Hi there</Box>
}

...
<Content><MyContentComponent /></Content>
...

NewTabPage

The NewTabPage component allows you to build out pieces of an application that will live on top of the existing NewTabPage. This Component acts like a way to render the plugin views into specific areas.

const MyNTPComponent = () => {
  return (
    <>
      <Setting ... />
      <Tab ... />
    </>
  )
}

...
<NewTabPage><MyNTPComponent /></NewTabPage>
...

Setting

Setting needs to be inside a NewTabPage

The Setting component allows plugins to access the settings modal, this means that the user can configure the plugin based on the users' needs.

...
<Setting
  title="My Plugin"
  description="You can enable disable the plugin"
  type="boolean"
  onChange={(e) => { ... }}
  value={isActive}
/>
...
Props
  • title (string) - The title for the plugin setting
  • description (string) - The description for the plugin setting
  • type ('boolean' | 'button') - Some type of input for user to interact with currently boolean will render a switch and button will render the button component.
  • onChange (function) - A function to handle a click or change of value.
  • value (boolean | string) - Either the value of the boolean flag or the string for the button call to action.

Tab

Tab needs to be inside a NewTabPage

The tab component allows plugins to render their content inside the New Tab Page. This is a great way to display some important information from the plugin or just add a nice new view. To the New Tab Page. The user will have to navigate to this tab to use.

<Tab title="My Plugin">
  <Box>I am in a tab</Box>
</Tab>

Player

The play is just a component that you should have access too for the ability to reuse the functionality.

TODO document player component more.

Refactor

  • Screen Time ( put into the plugin )
  • Predictive breaks

Drawbacks

The only issue with this is that it needs to have some enforced conventions. This can be done via some verbose documentation or linting rules ( or both ).

  • No hooks can be placed at a top level of this component because it might be perceived that the data would persist between background and other components.
  • Only certain hooks can be used in Background v. other Components.
  • There needs to be buy-in for the entire system. Eg. Using EmotionJS and Box.

Alternatives

  • There is a life cycle hook implementation that I had that looked like the configuration of Gatsby.
  • Have one let me know!

Adoption strategy

We would need to take this spec and create some extensive documentation around it. Also, give more insight into all the possible styles from Box and maybe some more in-depth of possible plugins.

I think having examples would be great. Not just functionality we refactor into plugins but also maybe some potential plugins.

There also might be some issues with the flow of getting plugins to work or how to get plugins into the core extension.

How we teach this

I am thinking it would be great to have workshops! It is not going to be a huge community so it might be fun to teach people in person. Also, have spectrum chat have a plugin-specific room.

Unresolved questions

This does not outline the usage of components like our typography and box component. I do think those should be exposed for plugin authors though.

Figuring out Content component more seems like plugin author would need access to open the modal, style it. To know if something else is already open and potentially other things. Explore taking the break timer model and see what kind of access it needs to work.

Should we look into not allowing access to chrome APIs directly for security reasons? Is it a bad thing.

How much should we enforce the current standards to plugin authors, mostly around styles and usage of Box?

Internationalization

Support for internationalization can do two things for this project.

  • Most obvious one, more language support.
  • Second is consolidating the places we put copy.

End screen Plugin Spec

  • Start Date: 10-29-19
  • RFC PR: (leave this empty)

Summary

The end screen is a super meaningful part of the user's journey in the application. So much so I think we need to give plugins a way to hook into this functionality. I also think it's a great time to expand the plugin functionality and expand it past its initial state.

// from a plugin
<EndScreen type="foo">
  <FooEndScreen />
</EndScreen>

Just for referance this is the end screen it shows up after the breath player finishes.

Screen Shot 2019-10-27 at 5 23 40 PM

Motivation

The end screen is some new functionality that is afforded to us via the new player. It is super useful in terms of asking for feedback. Potentially for onboarding and some other cool applications. I think opening up this functionality to plugins can be super beneficial.

Here are just a few use cases that I have thought up.

  • share a breath. example
  • feelings check-in
  • mindful quotes
  • post breath onboarding
  • nudges to take a walk or other mindful activities

I would not only like the ability to add my own end screen plugins but also open it up to the broader community as well.

Design Breakdown

There is only a few new concepts added here, and luckily there is already a plugin component that functions pretty close to this. The Tab plugin component behaves this way already which is to take in some props, then via a hook shot them up to the extension to register the Plugin Component in a top-level state. When the UI state is met the component is rendered. This even has the concepts of a tab name which know which tab registered to render.

see Tab Component for the Plugin Component.
see use-tabs for the hook.
see App for where the tab is ultimately rendered.

To implement an end screen plugin component we also need to allow for a default end screen. Right now we have an affirmations end screen. I think we should allow this end screen to work normally if there are not registered end screens, but we also want to allow to initial end screen to be overwritten. This means we need to introduce a default type of end screen which we can simply name "DEFAULT_END_SCREEN".

Introducing a type for an end screen means that we also will need to support ways for setting end screens types. Right now the primary way to interact with the main player is to deeplink into the player. Setting a type on a deep linked play should be as simple as adding a query param. Eg

chrome-extension://fnkknppdgnpdiihjnoccimnookejgeei/index.html?play=true&type=foo

This also raised the need to add in a way to set a type on an alarm, which will eventually trigger a notification. This is where it gets a little tricky because from my understanding there is not really a way to do this without needing a custom solution.

There are two steps to this project.

  • Build out basic component interface with only default type
  • Fill out types and allow for the passing of types in URL and though alarms.

Drawbacks

  • It seems like the easiest parts of this plugin component would accommodate 80% of the functionality. While the other 80% of the harder work. Eg. Figuring out how to reconcile cache with alarms firing while also not gather tons of cache. Meaning there is a good chance this is going to be half-implemented.
  • The more type of plugin interfaces we build like this the easier it is to introduces an infinite render loop.
  • Seems like conflicting plugins might arise all trying to take over the default namespace.

Alternatives

  • We can make end screen a place where plugins can not add to it. ( do nothing )
  • Half implement this, meaning only the first step ( I am actually thinking this is not a bad idea )
  • Not expose default end screen and only allow plugins to work with types.

Unresolved questions

  • Should we allow the last plugin to render to override the "default_screen_view" spot in the registered views or the opposite?

Dependabot can't resolve your JavaScript dependency files

Dependabot can't resolve your JavaScript dependency files.

As a result, Dependabot couldn't update your dependencies.

The error Dependabot encountered was:

Error whilst updating @testing-library/react-hooks in /packages/extension/package-lock.json:
404 Not Found - GET https://registry.npmjs.org/@mujo/ui - Not found

If you think the above is an error on Dependabot's side please don't hesitate to get in touch - we'll do whatever we can to fix it.

You can mention @dependabot in the comments below to contact the Dependabot team.

Screen Time tooltip is under player

Seems there is a z-indexing issue with tooltips.

Screen Shot 2019-08-03 at 5 21 03 PM

Might need to rethink tooltips with portals or, see if we can force the tooltip above everything.

Bring font into extension locally

Description

Right now we pull the fonts from an external source. To allow for better offline capabilities we should pull these fonts into the extension and reference them to allow fonts to work offline.

Hoist testing configuration

Right now testing is done in each individual package. We can elevate that to the top level of the repo to run all the tests in the repo. This would be faster and would allow us to gather some better insight into code coverage.

Dependabot can't resolve your JavaScript dependency files

Dependabot can't resolve your JavaScript dependency files.

As a result, Dependabot couldn't update your dependencies.

The error Dependabot encountered was:

Error whilst updating webpack-manifest-plugin in /packages/extension/package-lock.json:
404 Not Found - GET https://registry.npmjs.org/@mujo/ui - Not found

If you think the above is an error on Dependabot's side please don't hesitate to get in touch - we'll do whatever we can to fix it.

You can mention @dependabot in the comments below to contact the Dependabot team.

Add in props validations

Description

Currently, there are no props validation for react components, I think we should start adding in props validators to all new components to avoid any simple missing prop issues.

Setup build tool test.

The build tools are getting quite complex. It would be good to at least test some integration tests that the plugins build systems. Is working.

Cleanup test logs.

Introducing React 16.9 and removing some logging mocks brought some verbose logs to tests.

console.error node_modules/react-test-renderer/cjs/react-test-renderer.development.js:102
      Warning: An update to SubscriptionProvider inside a test was not wrapped in act(...).
      
      When testing, code that causes React state updates should be wrapped into act(...):
      
      act(() => {
        /* fire events that update state */
      });
      /* assert on the output */
      
      This ensures that you're testing the behavior the user would see in the browser. Learn more at https://fb.me/react-wrap-tests-with-act

Make switch more accessible.

Right now the new Switch component is a div. Need to take a pass and add in some accessibility to it. Like the ability to focus it, and be able to toggle it via keyboard.

Refactor use-extension hook.

Purpose

I am starting to see some better patterns with hooks now working with them for a couple of months. Right now use extension is pretty much a controller that harbors a bunch of methods and a centralized area for data.

Problem

The problem is currently that when use-extension is used in a component it will make a copy of itself. Meaning it will need to initialize and there is the possibility its data will be different than the other users of the hook.

Solution

To solve this we need to refactor the use-extension hook to use context and have a provider. That way we have the ability to share the data between all versions of the use-extension usage and we can then inject the data into other components just like the usage of containers with react.

See use subscription for an example.

Screen Shot 2019-08-04 at 10 06 05 AM

Changes

  • Rename to be more generic use-controller
    • make domains for controllers eg. settings, stats, payment
  • Refactor App, and Content to use the same type of hooks

Example usage

// key will be a segment of the data
// return will be methods and data in object
const { user, buy }  = useController('payment')

Breaktimer checkbox

When a user does not have a break timer setup the checkbox for breaktimer functions as an uncontrolled checkbox allowing you to have a check mark if you go over your limit.

Player size is not reseting

Description

Seems like the player size is no resizing back down. I am almost thinking there is something conflicting with the reset that makes things persist.

Screen Shot 2019-09-30 at 1 42 51 PM

Turn buy.js into require-able file.

https://github.com/jcblw/Mujo-extension/blob/master/public/buy.js

This is the file that Google gives us to setup IAP but its minified and not fun. It has already been slightly changed but is not great. Optimally we can analyze the file and then refactor it to be more readable, testable, and also be able to require it.

This is potentially something I would like to open-source since I am not able to find anything that does this.

https://www.npmjs.com/search?q=iap%20chrome

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.