🫡 Maintained fork of finch-graphql
Finch GraphQL is a library that allows you to build up a local GraphQL API that is accessible via client scripts of an web extension. When external messaging is setup you may even query Finch GraphQL from a connectable website.
npm install --save @finch-graphql/api @finch-graphql/client graphql
If you are planning to use Finch GraphQL with react you can also install our React hooks
npm install --save @finch-graphql/react
Traditional implementation of GraphQL pass queries through HTTP, Finch GraphQL passes these queries though the browsers messaging system.
Checkout an example extension.
Message passing is one of the main means of communication for content scripts to be able to access network request and access to local storage APIs like IndexDB. Using GraphQL gives you some powerful abilities when using this messaging.
- Declarative: Imperative code can be hard to structure. Background scripts can become a confusing when imperatively connecting to events. GraphQL resolvers gives you structure on how to write your background script.
- Error handling: If a error happens in the background script it will be surfaced to the client script. Writing code into resolvers GraphQL will catch the errors that happen on the background script.
- Common patterns: GraphQL and React are common technology for the web, and when using them in a web extension it strips away some of the nuance of building an extension. This make extension development more accessible to backend and frontend developers.
The FinchApi
class is a class that allows you to create an executable graphql schema. It is modeled to look just like the ApolloServer
class. The only required properties in the options are typeDefs
and resolvers
.
import { FinchApi } from '@finch-graphql/api';
// Define your schema
const typeDefs = `
input PermissionsInput {
permissions: [String!]
}
type Browser {
permissions($input: PermissionsInput)
}
type Query {
browser: Browser
}
`;
// Defined resolvers
const resolvers = {
Browser: (parent, { input }) => {
return browser.permissions.contains(input);
},
};
// Create the executable schema
const api = new FinchApi({
typeDefs,
resolver,
});
When initializing the api Finch has some options to be able to customize your API.
Option | Type | Description |
---|---|---|
messageKey | string | Use a custom message key instead of using the generic Finch-message |
attachMessages | boolean | Auto attach browser messaging. |
attachExternalMessages | boolean | Auto attach external browser messaging. |
middleware | MiddlewareFN[] | Middleware provided by graphql-middleware |
disableIntrospection | boolean | If true introspection queries will be turned off. |
rules | ValidationRules[] | GraphQL validation rules |
If you do not have any existing messages you may use the attachMessages
option to automatically attach to the runtime messages. If you have existing messages you will want to setup up the manual handler to ensure you are able to resolve async resolvers.
import { FinchMessageKey } from '@finch-graphql/types';
browser.runtime.on[External]Message.addListener(message => {
if (message.type === FinchMessageKey.Generic) {
return api.on[External]Message(message);
}
}, []);
This is the main reason for this library, it makes it super easy to query large amounts of data from the background script without sending multiple messages.
import { FinchClient } from '@finch-graphql/client';
const client = new FinchClient();
const GetBrowserPermission = `
query getBrowserPermission($input: PermissionsInput) {
browser {
permissions(input: $input)
}
}
`(async function main() {
const resp = await client.query(GetBrowserPermission, {
input: { permissions: ['geolocation'] },
});
if (resp.data?.browser?.permissions) {
// Do stuff with permissions
}
})();
There is two hooks available to use if you are using a React application. First is the useQuery hook.
import { useQuery } from '@finch-graphql/react';
const MyComponent = () => {
const { data, error } = useQuery<QueryTypes, VariableTypes>(
MyComponentQueryDoc,
{ variables: { enabled: true } }
);
if (error) {
return null;
}
return (
...
)
}
Testing between your background resolvers and client scripts is now super easy. Here is a snippet of code that will connect your background resolvers to the content scripts queries. Note this is using a jest mock.
import { backgroundApiInstance } from '~/background/graphql';
// This will connect the background resolvers to the client scripts when called.
export const connectBackgroundResolvers = () => {
browser.runtime.sendMessage = jest
.fn()
.mockImplementation((message, sender) =>
backgroundApiInstance.onMessage(message, sender),
);
};