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}