@extend-chrome/messages
An API for Chrome extension messaging that makes sense. Uses Promises and
Observables for convenience.
Table of Contents
Getting started
You will need to use a bundler like Rollup,
Parcel, or Webpack to include this library in the build of Chrome extension.
See
rollup-plugin-chrome-extension
for an easy way use Rollup to build your Chrome extension!
Installation
$ npm i @extend-chrome/messages
Usage
Send and receive messages using isomorphic message wrappers, or with a
traditional messages object.
import { getMessage } from '@extend-chrome/messages'
export const [sendNumber, numberStream, waitForNumber] = getMessage(
'NUMBER',
)
import { numberStream } from './messages'
numberStream.subscribe(([n, sender]) => {
console.log('the data passed to sendNumber', n)
console.log('the message sender', sender)
})
import { sendNumber } from './messages'
document.body.onclick = () => {
sendNumber(42)
}
getMessage
has great TypeScript support!
If you're into TypeScript, getMessage
is a generic function. It shines when
you define the message data type. No more message data type mistakes!
Intellisense has you covered.
import { getMessage } from '@extend-chrome/messages'
interface Stats {
hi: number
low: number
date: string
}
export const [sendStats, statsStream, waitForStats] = getMessage<Stats>('STATS')
export const [sendReady, readyStream, waitForReady] = getMessage<void>('READY')
import { statsStream } from './messages'
statsStream.subscribe(([{ hi, lo, date }, sender]) => {
})
waitForReady().then(() => {
console.log('content.ts is ready')
})
import { sendStats } from './messages'
sendStats({ hi: 30, low: 14, date: '11/12/2019' })
sendStats('not a Stats object')
sendReady()
Features
TypeScript Definitions
This library is written in TypeScript, extensively typed, and definitions are
included, so no need to install an additional @types
library!
RxJs Observables
Version 0.5.0 introduces an
RxJs Observable as
messages.stream
.
Scopes
Version 0.5.0 introduces getScope
, a way to use a separate
messaging space.
This is useful if you are writing a library for Chrome extensions that uses
messages internally, but you don't want to pollute the global messaging space.
API
getMessage(greeting)
import { getMessage } from '@extend-chrome/messages'
const [sendMessage, messageStream, waitForMessage] = getMessage('greeting')
Use this function to create an isomorphic message system. Import it into both
the message sender and receiver context (ie, the background page and a content
script). getMessage
is a TypeScript generic function. See the Usage
section for more information, including TypeScript support!
greeting
Type: string
A unique string to identify the message.
Returns: [messageSender, messageStream]
Type: [Function, Observable]
Import the messageSender into the context you wish to send a message. Call the
sender with the data you want to send.
messageStream
is an Observable of a [data, MessageSender]
tuple. Import the
messageStream into the context you wish to recieve messages. Subscribe to it
with a message handler function.
The messages
Namespace
If you're more comfortable with a traditional messages namespace, import the
messages
object.
messages.send(data, [options])
Sending one-way messages is simple: just call messages.send
with an object
that includes at least a greeting
property.
import { messages } from '@extend-chrome/messages'
messages.send({ greeting: 'hello' }).then(() => {
console.log('The message was sent.')
})
messages
.send({
greeting: 'with-data',
data: { x: 1 },
})
.then(() => {
console.log('The message was sent.')
})
Actually, you can send any data type as a message, but an object with a
greeting
prop is a nice, flexible pattern.
Get a response with options.async
Set the optional options.async
to true
to receive a response. Only message
listeners with the third sendResponse
argument will receive async messages.
import { messages } from '@extend-chrome/messages'
messages
.send(
{ greeting: 'hello' },
{ async: true },
)
.then((response) => {
console.log('They said', response.greeting)
})
messages.on(handler)
To receive one way messages, use a message handler function with 0 or 1
arguments. This handler will only receive messages sent without the async
option.
The return value of the handler is unused.
import * as messages from '@extend-chrome/messages'
messages.on((message, sender) => {
if (message.greeting === 'hello') {
console.log(sender.id, 'said hello')
}
})
Async Messages
I've found relying on async messages to be a bit of an anti-pattern. Chrome is
pretty aggressive about closing the response port, so unless you're doing
something synchronous, it's better to use a separate message and use a
listener to handle responses.
To receive async messages, use a message handler with 3 arguments. This handler
will only receive messages sent with the async option.
The third argument is a sendResponse
function, which must be called very
quickly, or Chrome will throw an error. Even a single await may make the extension unreliable.
messages.on(async (message, sender, sendResponse) => {
if (message.greeting === 'hello') {
console.log(sender.id, 'said hello')
await somethingAsync()
sendResponse({ greeting: 'goodbye' })
}
})
messages.off(handler)
Call this with the message handler function you wish to stop using.
messages.stream
Type: Observable
An Observable of all messages in its scope.
import { messages } from '@extend-chrome/messages'
messages.stream.subscribe(([message, sender, sendResponse]) => {
if (typeof sendResponse !== 'undefined') {
sendResponse({ greeting: 'message received!' })
}
})
getScope
This is useful if you are writing a library for Chrome extensions that uses
messages internally, but you don't want to pollute the global messaging space.
import { messages, getScope } from '@extend-chrome/messages'
const myScope = getScope('my-library')
myScope.send({ greeting: 'hey' })
messages.send({ greeting: 'hello?' })
Note: The Chrome API Event chrome.runtime.onMessage
will still receive all
messages, but projects using @extend-chrome/messages
will not receive messages from
other scopes.