1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
mod errors;

pub use crate::errors::Error;
use serde::Serialize;
use serde_json::{json, Value};
use std::fmt::Display;
use std::io;
use std::io::{Read, Write};
use std::panic;

/// Writes the given JSON data to stdout, thereby 'sending' a message
/// back to Chrome. *If you are on stable, then you also need to import macros
/// from the `serde_json` crate.*
///
/// # Example
///
/// ```
/// use chrome_native_messaging::send;
/// use serde_json::json;
///
/// send!({ "msg": "Hello, world!" });
/// ```
#[macro_export]
macro_rules! send {
    ($($json:tt)+) => {{
        let v = json!($($json),+);
        $crate::send_message(::std::io::stdout(), &v)
    }}
}

/// Reads input from a stream, decoded according to
/// Chrome's own documentation on native messaging.
/// (https://developer.chrome.com/extensions/nativeMessaging)
///
/// 1. A 32bit unsigned integer specifies how long the message is.
/// 2. The message is encoded in JSON
///
/// # Example
///
/// ```
/// use std::io;
/// use chrome_native_messaging::{read_input, Error};
///
/// read_input(io::stdin())
///     .err().expect("doctest should return unexpected eof");
///
pub fn read_input<R: Read>(mut input: R) -> Result<Value, Error> {
    let mut buf = [0; 4];
    match input.read_exact(&mut buf).map(|()| u32::from_ne_bytes(buf)) {
        Ok(length) => {
            //println!("Found length: {}", length);
            let mut buffer = vec![0; length as usize];
            input.read_exact(&mut buffer)?;
            let value = serde_json::from_slice(&buffer)?;
            Ok(value)
        }
        Err(e) => match e.kind() {
            io::ErrorKind::UnexpectedEof => Err(Error::NoMoreInput),
            _ => Err(e.into()),
        },
    }
}

/// Writes an output to a stream, encoded according to
/// Chrome's documentation on native messaging.
/// (https://developer.chrome.com/extensions/nativeMessaging)
/// Takes a custom value which implements serde::Serialize.
///
/// # Example
///
/// ```
/// use chrome_native_messaging::send_message;
/// use std::io;
/// use serde::Serialize;
/// use serde_json::json;
///
/// #[derive(Serialize)]
/// struct BasicMessage<'a> {
///     payload: &'a str
/// }
///
/// send_message(io::stdout(), &BasicMessage { payload: "Hello, World! "})
///     .expect("failed to send to stdout");
/// ```
pub fn send_message<W: Write, T: Serialize>(mut output: W, value: &T) -> Result<(), Error> {
    let msg = serde_json::to_string(value)?;
    let len = msg.len();
    // Chrome won't accept a message larger than 1MB
    if len > 1024 * 1024 {
        return Err(Error::MessageTooLarge { size: len });
    }
    let len = len as u32; // Cast is safe due to size check above
    let len_bytes = len.to_ne_bytes();
    output.write_all(&len_bytes)?;
    output.write_all(msg.as_bytes())?;
    output.flush()?;
    Ok(())
}

/// Handles a panic in the application code, by sending
/// a message back to Chrome before exiting.
fn handle_panic(info: &std::panic::PanicInfo) {
    let msg = match info.payload().downcast_ref::<&'static str>() {
        Some(s) => *s,
        None => match info.payload().downcast_ref::<String>() {
            Some(s) => &s[..],
            None => "Box<Any>",
        },
    };
    // Ignore error if send fails, we don't want to panic inside the panic handler
    let _ = send!({
        "status": "panic",
        "payload": msg,
        "file": info.location().map(|l| l.file()),
        "line": info.location().map(|l| l.line())
    });
}

/// Starts an 'event loop' which listens and writes to
/// stdin and stdout respectively.
///
/// Despite its name implying an asynchronous nature,
/// this function blocks waiting for input.
///
/// # Example
///
/// ```
/// use chrome_native_messaging::event_loop;
/// use std::io;
/// use serde::Serialize;
/// use serde_json::{json, Value};
///
/// #[derive(Serialize)]
/// struct BasicMessage<'a> {
///     payload: &'a str
/// }
///
/// event_loop(|value| match value {
///     Value::Null => Err("null payload"),
///     _ => Ok(BasicMessage { payload: "Hello, World!" })
/// });
///
/// ```
pub fn event_loop<T, E, F>(callback: F)
where
    F: Fn(serde_json::Value) -> Result<T, E>,
    T: Serialize,
    E: Display,
{
    panic::set_hook(Box::new(handle_panic));

    loop {
        // wait for input
        match read_input(io::stdin()) {
            Ok(v) => match callback(v) {
                Ok(response) => send_message(io::stdout(), &response).unwrap(),
                Err(e) => send!({ "error": format!("{}", e) }).unwrap(),
            },
            Err(e) => {
                // if the input stream has finished, then we exit the event loop
                if let Error::NoMoreInput = e {
                    break;
                }
                send!({ "error": format!("{}", e) }).unwrap();
            }
        }
    }
}