webextension_native_messaging/
lib.rs

1#![forbid(unsafe_code)]
2#![warn(missing_docs, clippy::missing_docs_in_private_items)]
3
4//! # WebExtension Native Messaging
5//!
6//! > **WebExtension native messaging library for Rust.**
7//!
8//! ## Reading
9//!
10//! In your web extension:
11//!
12//! ```js
13//! const port = browser.runtime.connectNative('native executable');
14//!
15//! port.postMessage('Hey, there!');
16//! ```
17//!
18//! Then in your native executable:
19//!
20//! ```rust,no_run
21//! use webextension_native_messaging::read_message;
22//!
23//! let message = read_message::<String>().unwrap();
24//! println!("{}", message);
25//! ```
26//!
27//! ## Writing
28//!
29//! In your web extension:
30//!
31//! ```js
32//! const port = browser.runtime.connectNative('native executable');
33//!
34//! port.onMessage.addListener((message) => {
35//!   console.log(message);
36//! });
37//! ```
38//!
39//! Then in your native executable:
40//!
41//! ```rust,no_run
42//! use webextension_native_messaging::write_message;
43//!
44//! let message = "Hey, there!".to_string();
45//! write_message(&message).unwrap();
46//! ```
47//!
48//! See the [native messaging documentation] for precise instructions on how to
49//! send and receive messages.
50//!
51//! [native messaging documentation]: https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Native_messaging
52
53use std::io::{Read, Write};
54
55use byteorder::{NativeEndian, ReadBytesExt, WriteBytesExt};
56
57/// All possible errors that can happen with reading or writing messages.
58#[derive(Debug, thiserror::Error)]
59pub enum MessagingError {
60  /// Infallible errors.
61  #[error(transparent)]
62  Infallible(#[from] std::convert::Infallible),
63  #[error(transparent)]
64  /// IO errors.
65  Io(#[from] std::io::Error),
66  #[error(transparent)]
67  /// JSON (de)serialization errors.
68  Json(#[from] serde_json::Error),
69  /// Integer parsing errors.
70  #[error(transparent)]
71  TryFromInt(#[from] std::num::TryFromIntError),
72}
73
74/// Read message function with a generic [`Read`]er so that we can test it
75/// without having to actually use standard in/out.
76pub(crate) fn generic_read_message<D, R>(
77  mut reader: R,
78) -> Result<D, MessagingError>
79where
80  D: for<'a> serde::Deserialize<'a>,
81  R: Read,
82{
83  let message_length = reader.read_u32::<NativeEndian>()?.try_into()?;
84  let message_bytes = reader.take(message_length);
85
86  serde_json::from_reader(message_bytes).map_err(Into::into)
87}
88
89/// Attempts to read a message from the program's stdin in the
90/// [native messaging] format.
91///
92/// [native messaging]: https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Native_messaging#app_side
93pub fn read_message<D>() -> Result<D, MessagingError>
94where
95  D: for<'a> serde::Deserialize<'a>,
96{
97  let stdin = std::io::stdin();
98  let stdin = stdin.lock();
99  generic_read_message(stdin)
100}
101
102/// Write message function with a generic [`Write`]r so that we can test it
103/// without having to actually use standard in/out.
104pub(crate) fn generic_write_message<S, W>(
105  message: &S,
106  mut writer: W,
107) -> Result<(), MessagingError>
108where
109  S: serde::Serialize,
110  W: Write,
111{
112  let message_bytes = serde_json::to_vec(message)?;
113  let message_length = message_bytes.len().try_into()?;
114
115  writer.write_u32::<NativeEndian>(message_length)?;
116  writer.write_all(&message_bytes)?;
117  writer.flush().map_err(MessagingError::Io)
118}
119
120/// Attempts to write a message to the program's stdout in the
121/// [native messaging] format.
122///
123/// [native messaging]: https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Native_messaging#app_side
124pub fn write_message<S>(message: &S) -> Result<(), MessagingError>
125where
126  S: serde::Serialize,
127{
128  let stdout = std::io::stdout();
129  let stdout = stdout.lock();
130  generic_write_message(message, stdout)
131}
132
133#[cfg(test)]
134pub(crate) mod tests {
135  use crate::{generic_read_message, generic_write_message, MessagingError};
136
137  #[derive(Debug, serde::Deserialize, serde::Serialize, PartialEq)]
138  struct Message {
139    text: String,
140  }
141
142  #[test]
143  fn test_messaging() -> Result<(), MessagingError> {
144    let test_message = Message {
145      text: "This is a test".to_string(),
146    };
147
148    // Create a buffer that will act as both the reader and writer
149    // (i.e. stdin and stdout).
150    let mut buffer: Vec<u8> = vec![];
151
152    // Write the message to the buffer.
153    generic_write_message(&test_message, &mut buffer)?;
154
155    // Then read the message, we get `std::io::Read` by dereferencing the
156    // `Vec<u8>` to `&[u8]`.
157    let message = generic_read_message::<Message, _>(&*buffer)?;
158
159    assert_eq!(message, test_message);
160    Ok(())
161  }
162}