Code Monkey home page Code Monkey logo

messages's Introduction

@extend-chrome/messages logo

@extend-chrome/messages

npm (scoped) GitHub last commit License TypeScript Declarations Included

Fiverr: We make Chrome extensions ko-fi


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, low, 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 { 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.

messages's People

Contributors

amysteam avatar filimoa avatar jacksteamdev avatar kyranjamie avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar

messages's Issues

Get error while using extend-chrome/messages while sending messgae from background script to content script

I have explained the problem in great detail over on stack overflow over here.

To describe here also in brief I am trying to send a URL object from background script to content script. The example in the documentation is from content script to background script. Anyways i tried to do the same shown in the documentation for background script to content script but there is error is console saying

Could not establish connection. Receiving end does not exist

Here is the copy of stackoverflow question which has all info

I have my background script like this

import { sendUrlInfo, waitForUrl } from 'utils/messaging';
import URL from 'url-parse';

chrome.webNavigation.onHistoryStateUpdated.addListener((details) => {
  const url = new URL(details.url);
  console.log('sending', url.hostname, url.pathname);
  sendUrlInfo(new URL(details.url));
});

chrome.webNavigation.onCompleted.addListener((details) => {
  if (details.url !== 'about:blank') {
    const url = new URL(details.url);
    console.log('sending', url.hostname, url.pathname);
    sendUrlInfo(new URL(details.url));
  }
});

And according to documentation i have message.js as shown below

import { getMessage } from '@extend-chrome/messages';
import URL from 'url-parse';

const messageTypes = {
  URL_INFO: 'URL_INFO',
};

export const [sendUrlInfo, urlStream, waitForUrl] = getMessage<URL>(
  messageTypes.URL_INFO,
);

and now content script I have written in react
So i am trying to subscribe to stream when the component is mounted

import React, { useState, useEffect, useRef } from 'react';

import Editor from 'content/components/Editor';
import Opener from 'content/components/Opener';
import { urlStream, waitForUrl } from 'utils/messaging';

const Main: React.FC = () => {
  const [isOpen, setIsOpen] = useState(false);
  const [showContent, setShowContent] = useState(false);

  const editorRef = useRef<HTMLTextAreaElement | null>(null);

  useEffect(() => {
    console.log('MOunted');
    urlStream.subscribe(([url]) => {
      console.log('Received', url.hostname, url.pathname);
      if (url.hostname === 'www.youtube.com' && url.pathname === '/watch') {
        if (!showContent) setShowContent(true);
      } else {
        if (showContent) setShowContent(false);
      }
    });
  }, []);

  useEffect(() => {
    document.addEventListener('keydown', onKeyPress);
    return () => {
      document.removeEventListener('keydown', onKeyPress);
    };
  });

  const onKeyPress = (e: KeyboardEvent) => {
    if (e.ctrlKey && e.which === 192) {
      e.preventDefault();
      setIsOpen(!isOpen);
    }
  };

  if (!showContent) return <></>;

  return (
    <>
      <div>test</div>
      <Opener isOpen={isOpen} onClick={() => setIsOpen(true)} />
      <Editor
        isOpen={isOpen}
        ref={editorRef}
        onClose={() => setIsOpen(false)}
      />
    </>
  );
};

export default Main;

The error I am getting in background script console is

Could not establish connection. Receiving end does not exist

I think as much as i understand the background script is trying to send msg but the content script observable is not subscribed yet. As content script run after the page is loaded.
If this is the issue is there any way to use this library properly.

BTW this works if we use normal chrome api like this

chrome.tabs.query({ active: true, currentWindow: true }, function (tabs) {
	chrome.tabs.sendMessage(
	    tabs[0].id,
		message,
		function () {
				console.log("msg sent")		
		}
	);
});

and use onMessage to receive the msg.

send message to specific tab

Is your feature request related to a problem? Please describe.

I'd like to send message from background/popup to specific content tab

Describe the solution you'd like

I'd like send method can handle option.tabId

Describe alternatives you've considered

Or another method send*ToContent(tab.id, payload, options)

Additional context

Wrong type for getMessage(...).wait

Google search terms

I only went through files here.

Describe the bug

getMessage(...).wait will return the same i.e. [data, sender] tuple, as does firstValueFrom(getMessage(...).stream)
but the types are annotated incorrectly

How do we reproduce?

const [_, stream, wait] = getMessage("a")
const waited = wait()
const streamed = firstValueFrom(stream)

Expected behavior

the same type

Actual behavior

the type infer to Promise and Promise<[string, chrome.runtime.MessageSender]> even though the output is the same

Screenshots

Please complete the following information:

  • Library Version: 1.2.2
  • Operating System: Mac
  • Browser: N/A (but Brave)
  • Node Version: v21.7.1

Additional context

[mv3] Using this package from a chrome.scripting.executeScript file

Hello again! Following up the discussion at crxjs/chrome-extension-tools#124. I'm now trying to use @extend-chome/messages and the built file ends up importing another file like this:

import { s as sendNumber } from './chunks/messages-2397a427.js';

This, in turn, throws Cannot use import statement outside a module. As you already may know from the linked issue, content-script.ts was added as an input file in rollup.config.js. I tried returning an array from rollup config by having a configuration for content-script.ts and another for manifest.ts and that kind of works but simpleReloader is now working right.

rollup.config.js looks like this:

import path from 'path'

import resolve from '@rollup/plugin-node-resolve'
import commonjs from '@rollup/plugin-commonjs'
import typescript from '@rollup/plugin-typescript'

import { chromeExtension, simpleReloader } from 'rollup-plugin-chrome-extension'
import { emptyDir } from 'rollup-plugin-empty-dir'
import zip from 'rollup-plugin-zip'
import replace from '@rollup/plugin-replace'

const isProduction = process.env.NODE_ENV === 'production'

export default {
  input: ['src/manifest.ts', 'src/content-script.ts'],
  output: {
    dir: 'dist',
    format: 'esm',
    chunkFileNames: path.join('chunks', '[name]-[hash].js'),
  },
  plugins: [
    replace({
      'process.env.NODE_ENV': isProduction
        ? JSON.stringify('production')
        : JSON.stringify('development'),
      preventAssignment: true,
    }),
    chromeExtension(),
    // Adds a Chrome extension reloader during watch mode
    simpleReloader(),
    resolve(),
    commonjs(),
    typescript(),
    // Empties the output dir before a new build
    emptyDir(),
    // Outputs a zip file in ./releases
    isProduction && zip({ dir: 'releases' }),
  ],
}

Thank you!

sendResponse from getMessage API

First and foremost, this project is amazing and a real lifesaver for my app.

I'm trying to await a response from the getMessage API but can't seem to find a way to do it. Using the messages API I can, but I'm unsure how to use typescript to guard the payload with it. How can I leverage the ability to send a response, while also leveraging the ability to type check payloads?

For example:

import { getMessage } from '@extend-chrome/messages';

interface Payload {
  arg: 1;
}

export const [sendReady, readyStream, waitForReady] = getMessage<Payload>('READY');

// background
sendReady.then((response) => console.log(response)); // { ping: true }

// content
readyStream.subscribe(([{ arg }, sender], sendResponse)) => {
  sendResponse({ ping: true }); 

  OR

  return { ping: true };
});

Thank you!

Migrate to @extend-chrome

We're migrating this library over to a new and more aptly named organization: extend-chrome!

TODO

  1. Catalog bumble-org dependencies
  2. Update README.md
  3. Update package.json
  4. Transfer GitHub repo
  5. Transfer NPM package

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.