bark_dev/
bark.rs

1// MIT License
2//
3// Copyright (c) 2025 66f94eae
4//
5// Permission is hereby granted, free of charge, to any person obtaining a copy
6// of this software and associated documentation files (the "Software"), to deal
7// in the Software without restriction, including without limitation the rights
8// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9// copies of the Software, and to permit persons to whom the Software is
10// furnished to do so, subject to the following conditions:
11//
12// The above copyright notice and this permission notice shall be included in all
13// copies or substantial portions of the Software.
14//
15// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21// SOFTWARE.
22
23use crate::msg::Msg;
24
25
26const TOKEN_OFFSET: u64 = 2700;
27const TEAM_ID: &str = "5U8LBRXG3A";
28const AUTH_KEY_ID: &str = "LH4T9V5U4R";
29const TOPIC: &str = "me.fin.bark";
30
31const KEY: &str = r#"-----BEGIN PRIVATE KEY----- 
32MIGTAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBHkwdwIBAQQg4vtC3g5L5HgKGJ2+ 
33T1eA0tOivREvEAY2g+juRXJkYL2gCgYIKoZIzj0DAQehRANCAASmOs3JkSyoGEWZ 
34sUGxFs/4pw1rIlSV2IC19M8u3G5kq36upOwyFWj9Gi3Ejc9d3sC7+SHRqXrEAJow 
358/7tRpV+ 
36-----END PRIVATE KEY-----
37"#;
38pub struct Bark {
39    team_id: String,
40    auth_key_id: String,
41    topic: String,
42    key: String,
43    token: String
44}
45
46
47impl Bark {
48    pub fn new() -> Self {
49        Self {
50            team_id : TEAM_ID.to_string(),
51            auth_key_id : AUTH_KEY_ID.to_string(),
52            topic : TOPIC.to_string(),
53            key : KEY.to_string(),
54            token : ".".to_string(),
55        }
56    }
57
58    pub fn born(timestamp: u64, token: String) -> Self {
59        Self {
60            team_id : TEAM_ID.to_string(),
61            auth_key_id : AUTH_KEY_ID.to_string(),
62            topic: TOPIC.to_string(),
63            key: KEY.to_string(),
64            token: format!("{}.{}", timestamp, token),
65        }
66    }
67
68    /// get apns token
69    /// 
70    /// return (create_timestamp, token)
71    pub fn token(&mut self) -> (u64, String) {
72        let token = self.token.split_once(".").unwrap();
73        (token.0.parse::<u64>().unwrap_or_else(|_e| 0), token.1.to_string())
74    }
75
76    /// force refresh apns token
77    /// 
78    /// return (create_timestamp, token)
79    pub fn force_refresh_token(&mut self) -> (u64, String) {
80        self.get_token();
81        self.token()
82    }
83    /// send msg to devices
84    /// 
85    /// return : None if success, or a vector of failed devices and error messages
86    pub fn send(&mut self, msg: &Msg, devices: &Vec<&str>) -> Option<Vec<String>> {
87        crate::apns::send(&msg, self.topic.clone().as_str(), &self.get_token(), devices)
88    }
89
90    /// async send to devices
91    /// 
92    /// return : None if success, or a vector of failed devices and error messages
93    pub async fn async_send(&mut self, msg: &Msg, devices: &Vec<&str>) -> Option<Vec<String>> {
94        crate::apns::async_send(&msg, self.topic.clone().as_str(), &self.get_token(), devices).await
95    }
96
97    fn get_token(&mut self) -> String {
98        let time_stamp: u64 = std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).expect("get token failed").as_secs();
99
100        if let Some((ts, token)) = self.token.split_once(".") {
101            // cache the token in memory for TOKEN_OFFSET[default is 2700] seconds
102            if ts.parse::<u64>().unwrap_or_else(|_e| 0) + TOKEN_OFFSET >= time_stamp {
103                return token.to_string();
104            }
105        }
106        
107        let jwt_header: String = Self::clean_str(
108            openssl::base64::encode_block(
109                format!("{{ \"alg\": \"ES256\", \"kid\": \"{}\" }}", self.auth_key_id)
110                .as_bytes()
111            )
112        );
113
114        let jwt_claims: String = Self::clean_str(
115            openssl::base64::encode_block(
116                format!("{{ \"iss\": \"{}\", \"iat\": {} }}", 
117                        self.team_id, time_stamp
118                    )
119                .as_bytes()
120            )
121        );
122
123        let mut singer: openssl::sign::Signer<'_> = openssl::sign::Signer::new(
124                                openssl::hash::MessageDigest::sha256(),
125                                &openssl::pkey::PKey::from_ec_key(
126                                                openssl::ec::EcKey::private_key_from_pem(self.key.as_bytes()).expect("init key data failed")
127                                            ).expect("generate private key failed")
128                                ).expect("init signer failed");
129
130        let jwt_header: String = format!("{}.{}", jwt_header, jwt_claims);
131        singer.update(jwt_header.as_bytes()).expect("fill sign data failed");
132        let sign: Vec<u8> = singer.sign_to_vec().expect("sign failed");
133        let jwt_signature: String = Self::clean_str(openssl::base64::encode_block(&sign));
134        let token: String= format!("{}.{}", jwt_header, jwt_signature);
135
136        self.token = format!("{}.{}", time_stamp, token);
137        token
138    }
139    
140    fn clean_str(str: String) -> String {
141        str.replace("+", "-")
142            .replace("/", "_")
143            .replace("=", "")
144    }
145   
146
147}