@extend-chrome/messages
v1.2.2
Published
An API for Chrome extension messages that makes sense.
Downloads
1,956
Maintainers
Readme
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.
// messages.js, used in both the background and content script
import { getMessage } from '@extend-chrome/messages'
// getMessage returns [Function, Observable, Function]
export const [sendNumber, numberStream, waitForNumber] = getMessage(
// String to be used as a greeting
'NUMBER',
)
// background.js, a background script
import { numberStream } from './messages'
// numberStream is an RxJs Observable
numberStream.subscribe(([n, sender]) => {
console.log('the data passed to sendNumber', n)
// Sender is a Chrome runtime MessageSender
console.log('the message sender', sender)
})
// content.ts, a content script
import { sendNumber } from './messages'
document.body.onclick = () => {
sendNumber(42) // 42 is logged in the background
}
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.
// messages.ts
import { getMessage } from '@extend-chrome/messages'
interface Stats {
hi: number
low: number
date: string
}
export const [sendStats, statsStream, waitForStats] = getMessage<Stats>('STATS')
// If you have a message type with no data, use void rather than undefined
// This way you can call it with zero arguments
export const [sendReady, readyStream, waitForReady] = getMessage<void>('READY')
// background.ts
import { statsStream } from './messages'
statsStream.subscribe(([{ hi, lo, date }, sender]) => {
// Intellisense knows this is an Observable of
// [Stats, chrome.runtime.MessageSender]
})
waitForReady().then(() => {
console.log('content.ts is ready')
})
// content.ts
import { sendStats } from './messages'
sendStats({ hi: 30, low: 14, date: '11/12/2019' })
// Throws a TS error
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.
// content-script.js
import { messages } from '@extend-chrome/messages'
// Simple message with no data
messages.send({ greeting: 'hello' }).then(() => {
console.log('The message was sent.')
})
// Message with data
messages
.send({
greeting: 'with-data',
// You can use any prop name or value
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.
// content-script.js
import { messages } from '@extend-chrome/messages'
messages
.send(
// Message
{ greeting: 'hello' },
// Options
{ 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.
// background.js
import * as messages from '@extend-chrome/messages'
// Listener should have 2, 1, or 0 arguments
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.
// Async functions are OK!
messages.on(async (message, sender, sendResponse) => {
if (message.greeting === 'hello') {
console.log(sender.id, 'said hello')
await somethingAsync()
// Still need to call sendResponse
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'
// Receives all messages in the default scope
messages.stream.subscribe(([message, sender, sendResponse]) => {
if (typeof sendResponse !== 'undefined') {
// If sendResponse is defined, it must be called
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')
// `messages.on` will not receive this message
myScope.send({ greeting: 'hey' })
// `myScope.on` will not receive this message
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.