mujo-code / chrome-extension Goto Github PK
View Code? Open in Web Editor NEWMujō is a Chrome extension that reminds you not to over work yourself.
Home Page: https://getmujo.com/
License: MIT License
Mujō is a Chrome extension that reminds you not to over work yourself.
Home Page: https://getmujo.com/
License: MIT License
Clicking on the Browser Action button should open up the NTP
In progress... see progress here
https://www.notion.so/jjcblw/e4c79acfc26344278f94148d2b27ac2c?v=a774bf4be0054b5a9466010135e48f23
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.
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
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.
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 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.
This should not entail much work. It actually should just hook into the existing functionality, but wrap it in effects.
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.
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.
The plugins resolver will do a few things on build time.
// example of the generated file.
export default {
'mujo-screen-time': safeRequire('../plugins/mujo-screen-time'),
'mujo-predictive-breaks': safeRequire('mujo-predictive-breaks'),
}
plugins
folder locally or from an npm module.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 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.
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)
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.
})
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)
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
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 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
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.
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>
...
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>
...
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 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}
/>
...
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>
The play is just a component that you should have access too for the ability to reuse the functionality.
TODO document player component more.
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 ).
life cycle hook
implementation that I had that looked like the configuration of Gatsby.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.
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.
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?
Right now it seems like the useStorage hook is not getting updates from the background.
Support for internationalization can do two things for this project.
This looks interesting thing to add for E2E tests.
https://github.com/smooth-code/jest-puppeteer/blob/master/packages/expect-puppeteer/README.md#api
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.
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.
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.
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.
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.
I have heard from a few people that they uninstall the application because they wanted to have a different new tab page. Since there are more capabilities to Mujo then just the NTP it would be nice for users to only conditionally override the NTP page.
https://kushagra.dev/blog/conditional-newtab-override-chrome-extension/
Here is an article about it.
Right now we have some overriding of some logging to avoid some weird act
logs. See more in linked issues.
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.
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.
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.
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.
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.
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
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.
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.
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.
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.
use-controller
// key will be a segment of the data
// return will be methods and data in object
const { user, buy } = useController('payment')
This error here is not being caught it is caused by users not being logged into the Chrome browser. We might need to figure out a way to handle this in the UI as well. Eg, not show subscription information.
https://sentry.io/share/issue/60304b8597a344a991d45428b04f432a/
https://www.npmjs.com/package/react-feather this might be cool to use. Looking to revist the NTP so this might help.
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.
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.
The default is zero so it often can create instances when 01 is entered into the form field. We want to somehow get rid of that zero issue.
https://developer.chrome.com/apps/i18n
This is going to give a lot more functionality, and also allow us to translate the web store listing.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.