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 std::time::Duration;
24
25use crate::msg::Msg;
26
27
28const TOKEN_OFFSET: u64 = 2700;
29const TEAM_ID: &str = "5U8LBRXG3A";
30const AUTH_KEY_ID: &str = "LH4T9V5U4R";
31const TOPIC: &str = "me.fin.bark";
32
33const KEY: &str = r#"-----BEGIN PRIVATE KEY----- 
34MIGTAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBHkwdwIBAQQg4vtC3g5L5HgKGJ2+ 
35T1eA0tOivREvEAY2g+juRXJkYL2gCgYIKoZIzj0DAQehRANCAASmOs3JkSyoGEWZ 
36sUGxFs/4pw1rIlSV2IC19M8u3G5kq36upOwyFWj9Gi3Ejc9d3sC7+SHRqXrEAJow 
378/7tRpV+ 
38-----END PRIVATE KEY-----
39"#;
40pub struct Bark {
41    team_id: String,
42    auth_key_id: String,
43    topic: String,
44    key: String,
45    token: String
46}
47
48
49impl Bark {
50    pub fn new() -> Self {
51        Self {
52            team_id : TEAM_ID.to_string(),
53            auth_key_id : AUTH_KEY_ID.to_string(),
54            topic : TOPIC.to_string(),
55            key : KEY.to_string(),
56            token : ".".to_string(),
57        }
58    }
59
60    pub fn born(timestamp: u64, token: String) -> Self {
61        if timestamp + TOKEN_OFFSET <= Self::ts() {
62            println!("warning: token expired, bark will new one");
63            return Self::new();
64        }
65        Self {
66            team_id : TEAM_ID.to_string(),
67            auth_key_id : AUTH_KEY_ID.to_string(),
68            topic: TOPIC.to_string(),
69            key: KEY.to_string(),
70            token: format!("{}.{}", timestamp, token),
71        }
72    }
73
74    fn ts() -> u64 {
75        std::time::SystemTime::now()
76            .duration_since(std::time::UNIX_EPOCH)
77            .unwrap_or_else(|_e| Duration::from_secs(0))
78            .as_secs()
79    }
80
81    /// get apns token
82    /// 
83    /// return (create_timestamp, token)
84    pub fn token(&mut self) -> (u64, String) {
85        let token = self.token.split_once(".").unwrap();
86        (token.0.parse::<u64>().unwrap_or_else(|_e| 0), token.1.to_string())
87    }
88
89    /// force refresh apns token
90    /// 
91    /// return (create_timestamp, token)
92    pub fn force_refresh_token(&mut self) -> (u64, String) {
93        self.get_token();
94        self.token()
95    }
96    /// send msg to devices
97    /// 
98    /// return : None if success, or a vector of failed devices and error messages
99    pub fn send(&mut self, msg: &Msg, devices: &Vec<&str>) -> Option<Vec<String>> {
100        crate::apns::send(&msg, self.topic.clone().as_str(), &self.get_token(), devices)
101    }
102
103    /// async send to devices
104    /// 
105    /// return : None if success, or a vector of failed devices and error messages
106    pub async fn async_send(&mut self, msg: &Msg, devices: &Vec<&str>) -> Option<Vec<String>> {
107        crate::apns::async_send(&msg, self.topic.clone().as_str(), &self.get_token(), devices).await
108    }
109
110    fn get_token(&mut self) -> String {
111        let time_stamp: u64 = Self::ts(); 
112
113        if let Some((ts, token)) = self.token.split_once(".") {
114            // cache the token in memory for TOKEN_OFFSET[default is 2700] seconds
115            if ts.parse::<u64>().unwrap_or_else(|_e| 0) + TOKEN_OFFSET >= time_stamp {
116                return token.to_string();
117            }
118        }
119        
120        let jwt_header: String = Self::clean_str(
121            openssl::base64::encode_block(
122                format!("{{ \"alg\": \"ES256\", \"kid\": \"{}\" }}", self.auth_key_id)
123                .as_bytes()
124            )
125        );
126
127        let jwt_claims: String = Self::clean_str(
128            openssl::base64::encode_block(
129                format!("{{ \"iss\": \"{}\", \"iat\": {} }}", 
130                        self.team_id, time_stamp
131                    )
132                .as_bytes()
133            )
134        );
135
136        let mut singer: openssl::sign::Signer<'_> = openssl::sign::Signer::new(
137                                openssl::hash::MessageDigest::sha256(),
138                                &openssl::pkey::PKey::from_ec_key(
139                                                openssl::ec::EcKey::private_key_from_pem(self.key.as_bytes()).expect("init key data failed")
140                                            ).expect("generate private key failed")
141                                ).expect("init signer failed");
142
143        let jwt_header: String = format!("{}.{}", jwt_header, jwt_claims);
144        singer.update(jwt_header.as_bytes()).expect("fill sign data failed");
145        let sign: Vec<u8> = singer.sign_to_vec().expect("sign failed");
146        let jwt_signature: String = Self::clean_str(openssl::base64::encode_block(&sign));
147        let token: String= format!("{}.{}", jwt_header, jwt_signature);
148
149        self.token = format!("{}.{}", time_stamp, token);
150        token
151    }
152    
153    fn clean_str(str: String) -> String {
154        str.replace("+", "-")
155            .replace("/", "_")
156            .replace("=", "")
157    }
158   
159
160}