fxa_client/internal/commands/
send_tab.rs

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
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

use super::super::telemetry;
/// The Send Tab functionality is backed by Firefox Accounts device commands.
/// A device shows it can handle "Send Tab" commands by advertising the "open-uri"
/// command in its on own device record.
/// This command data bundle contains a one-time generated `PublicCommandKeys`
/// (while keeping locally `PrivateCommandKeys` containing the private key),
/// wrapped by the account oldsync scope `kSync` to form a `CommandKeysPayload`.
///
/// When a device sends a tab to another, it decrypts that `CommandKeysPayload` using `kSync`,
/// uses the obtained public key to encrypt the `SendTabPayload` it created that
/// contains the tab to send and finally forms the encrypted payload that is
/// then sent to the target device.
use serde_derive::*;

pub const COMMAND_NAME: &str = "https://identity.mozilla.com/cmd/open-uri";

#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct SendTabPayload {
    pub entries: Vec<TabHistoryEntry>,
    #[serde(rename = "flowID", default)]
    pub flow_id: String,
    #[serde(rename = "streamID", default)]
    pub stream_id: String,
}

impl From<SendTabPayload> for crate::SendTabPayload {
    fn from(payload: SendTabPayload) -> Self {
        crate::SendTabPayload {
            entries: payload.entries.into_iter().map(From::from).collect(),
            flow_id: payload.flow_id,
            stream_id: payload.stream_id,
        }
    }
}

impl SendTabPayload {
    pub fn single_tab(title: &str, url: &str) -> (Self, telemetry::SentCommand) {
        let sent_telemetry: telemetry::SentCommand = telemetry::SentCommand::for_send_tab();
        (
            SendTabPayload {
                entries: vec![TabHistoryEntry {
                    title: title.to_string(),
                    url: url.to_string(),
                }],
                flow_id: sent_telemetry.flow_id.clone(),
                stream_id: sent_telemetry.stream_id.clone(),
            },
            sent_telemetry,
        )
    }
}

#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct TabHistoryEntry {
    pub title: String,
    pub url: String,
}

impl From<TabHistoryEntry> for crate::TabHistoryEntry {
    fn from(e: TabHistoryEntry) -> Self {
        crate::TabHistoryEntry {
            title: e.title,
            url: e.url,
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_minimal_parse_payload() {
        let minimal = r#"{ "entries": []}"#;
        let payload: SendTabPayload = serde_json::from_str(minimal).expect("should work");
        assert_eq!(payload.flow_id, "".to_string());
    }

    #[test]
    fn test_payload() {
        let (payload, telem) = SendTabPayload::single_tab("title", "http://example.com");
        let json = serde_json::to_string(&payload).expect("should work");
        assert_eq!(telem.flow_id.len(), 12);
        assert_eq!(telem.stream_id.len(), 12);
        assert_ne!(telem.flow_id, telem.stream_id);
        let p2: SendTabPayload = serde_json::from_str(&json).expect("should work");
        // no 'PartialEq' derived so check each field individually...
        assert_eq!(payload.entries[0].url, "http://example.com".to_string());
        assert_eq!(payload.flow_id, p2.flow_id);
        assert_eq!(payload.stream_id, p2.stream_id);
    }
}