Commit c2f4c320 by Anand.suthar

Fix issues

parent 53b026ff
......@@ -7,7 +7,7 @@
<key>Bhagyashree.xcscheme</key>
<dict>
<key>orderHint</key>
<integer>30</integer>
<integer>34</integer>
</dict>
</dict>
</dict>
......
......@@ -7,34 +7,34 @@
<key>AD_UNIT_ID_FOR_INTERSTITIAL_TEST</key>
<string>ca-app-pub-3940256099942544/4411468910</string>
<key>CLIENT_ID</key>
<string>948213756039-ctnseb32i76tvtuc8kvn3c6l09dpr9qf.apps.googleusercontent.com</string>
<string>360669556539-gjg3t78lg0vnaqcis9o26vsrii77d9qn.apps.googleusercontent.com</string>
<key>REVERSED_CLIENT_ID</key>
<string>com.googleusercontent.apps.948213756039-ctnseb32i76tvtuc8kvn3c6l09dpr9qf</string>
<string>com.googleusercontent.apps.360669556539-gjg3t78lg0vnaqcis9o26vsrii77d9qn</string>
<key>API_KEY</key>
<string>AIzaSyDJqRanYPwGFARc6XQ5GUC5pUuBDgJm0D4</string>
<string>AIzaSyBbcoObffadmduuSAhDozLyWbyiqg7Mf9s</string>
<key>GCM_SENDER_ID</key>
<string>948213756039</string>
<string>360669556539</string>
<key>PLIST_VERSION</key>
<string>1</string>
<key>BUNDLE_ID</key>
<string>com.gdi.yuanzhongsiu-seller</string>
<key>PROJECT_ID</key>
<string>fengshui-9263b</string>
<string>fengshui-customer</string>
<key>STORAGE_BUCKET</key>
<string>fengshui-9263b.appspot.com</string>
<string>fengshui-customer.appspot.com</string>
<key>IS_ADS_ENABLED</key>
<true></true>
<true/>
<key>IS_ANALYTICS_ENABLED</key>
<false></false>
<false/>
<key>IS_APPINVITE_ENABLED</key>
<false></false>
<false/>
<key>IS_GCM_ENABLED</key>
<true></true>
<true/>
<key>IS_SIGNIN_ENABLED</key>
<true></true>
<true/>
<key>GOOGLE_APP_ID</key>
<string>1:948213756039:ios:84a8dd99e8502ac3</string>
<string>1:360669556539:ios:84a8dd99e8502ac3</string>
<key>DATABASE_URL</key>
<string>https://fengshui-9263b.firebaseio.com</string>
<string>https://fengshui-customer.firebaseio.com</string>
</dict>
</plist>
\ No newline at end of file
</plist>
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>AD_UNIT_ID_FOR_BANNER_TEST</key>
<string>ca-app-pub-3940256099942544/2934735716</string>
<key>AD_UNIT_ID_FOR_INTERSTITIAL_TEST</key>
<string>ca-app-pub-3940256099942544/4411468910</string>
<key>CLIENT_ID</key>
<string>360669556539-gjg3t78lg0vnaqcis9o26vsrii77d9qn.apps.googleusercontent.com</string>
<key>REVERSED_CLIENT_ID</key>
<string>com.googleusercontent.apps.360669556539-gjg3t78lg0vnaqcis9o26vsrii77d9qn</string>
<key>API_KEY</key>
<string>AIzaSyBbcoObffadmduuSAhDozLyWbyiqg7Mf9s</string>
<key>GCM_SENDER_ID</key>
<string>360669556539</string>
<key>PLIST_VERSION</key>
<string>1</string>
<key>BUNDLE_ID</key>
<string>com.gdi.yuanzhongsiu-seller</string>
<key>PROJECT_ID</key>
<string>fengshui-customer</string>
<key>STORAGE_BUCKET</key>
<string>fengshui-customer.appspot.com</string>
<key>IS_ADS_ENABLED</key>
<true/>
<key>IS_ANALYTICS_ENABLED</key>
<false/>
<key>IS_APPINVITE_ENABLED</key>
<false/>
<key>IS_GCM_ENABLED</key>
<true/>
<key>IS_SIGNIN_ENABLED</key>
<true/>
<key>GOOGLE_APP_ID</key>
<string>1:360669556539:ios:84a8dd99e8502ac3</string>
<key>DATABASE_URL</key>
<string>https://fengshui-customer.firebaseio.com</string>
</dict>
</plist>
......@@ -17,7 +17,7 @@
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleVersion</key>
<string>10</string>
<string>12</string>
<key>Fabric</key>
<dict>
<key>APIKey</key>
......
......@@ -11,6 +11,7 @@ import UIKit
protocol EditResultPhotoPickerDelegate {
func addNewImage()
func removeImage(index: Int)
func previewImages(index: Int)
}
class EditResultPhotoPicker: UITableViewCell, UICollectionViewDelegate, UICollectionViewDataSource {
......@@ -69,6 +70,9 @@ extension EditResultPhotoPicker: EditResultPhotoPickerCellDelegate {
func removeImage(index: Int) {
self.delegate.removeImage(index: index)
}
func previewImages(index: Int) {
self.delegate.previewImages(index: index)
}
}
......@@ -83,6 +87,7 @@ extension EditResultPhotoPicker: EditResultPhotoPickerCellDelegate {
protocol EditResultPhotoPickerCellDelegate {
func removeImage(index: Int)
func previewImages(index: Int)
}
class EditResultPhotoPickerCell: UICollectionViewCell {
......@@ -95,4 +100,8 @@ class EditResultPhotoPickerCell: UICollectionViewCell {
self.delegate.removeImage(index: img.tag)
}
@IBAction func previewImage() {
self.delegate.previewImages(index: img.tag)
}
}
......@@ -10,6 +10,7 @@ import UIKit
import Kingfisher
import AVFoundation
import SVProgressHUD
import INSPhotoGallery
class EditResultViewController: BaseViewController {
......@@ -220,8 +221,30 @@ extension EditResultViewController: EditResultPhotoPickerDelegate{
imagesForResult.remove(at: index)
self.tblresult.reloadData()
}
func previewImages(index: Int) {
var photos = [INSPhoto]()
for image in imagesForResult {
let photo = INSPhoto(image: image, thumbnailImage: nil)
photos.append(photo)
}
let galleryPreview = INSPhotosViewController(photos: photos, initialPhoto: nil, referenceView: self.navigationController?.view)
self.present(galleryPreview, animated: true, completion: nil)
galleryPreview.referenceViewForPhotoWhenDismissingHandler = { photo in
return nil
}
}
}
extension EditResultViewController: UIImagePickerControllerDelegate, UINavigationControllerDelegate {
func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
......@@ -249,9 +272,6 @@ extension EditResultViewController: EditResultProductCellDelegate {
extension EditResultViewController: AudioRecordingCellDelegate {
func recordedAudio(filePath: String) {
print(filePath)
let audioRecording = AudioRecording(audioPath: filePath)
self.audioRecordings.append(audioRecording)
tblresult.reloadData()
......
//
// OnGoingServicesViewController.swift
// FengshuiLayout
//
// Created by SunarcMAC on 18/05/18.
// Copyright © 2018 SunarcMAC. All rights reserved.
//
import UIKit
import Kingfisher
import MJRefresh
class InvalidServicesViewController: BaseViewController {
@IBOutlet var tblInvalidServices: UITableView!
let service = Service()
var footerForLoading: MJRefreshAutoNormalFooter!
override func viewDidLoad() {
super.viewDidLoad()
service.isBooking = true
self.setupRefreshHeaderFooter()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
if service.services.count == 0 {
tblInvalidServices.mj_header.beginRefreshing()
}
}
func setupRefreshHeaderFooter() {
let headerForLoading = MJRefreshNormalHeader {
self.services(nextPage: false)
}
headerForLoading?.activityIndicatorViewStyle = .gray
headerForLoading?.lastUpdatedTimeLabel.isHidden = true
headerForLoading?.stateLabel.isHidden = true
tblInvalidServices.mj_header = headerForLoading
footerForLoading = MJRefreshAutoNormalFooter {
self.services(nextPage: true)
}
footerForLoading.isRefreshingTitleHidden = true
footerForLoading.activityIndicatorViewStyle = .gray
footerForLoading.stateLabel.isHidden = true
tblInvalidServices.mj_footer = footerForLoading
}
func services(nextPage: Bool) {
if nextPage == true {
service.nextPage(serviceStatus: .COMPLETE, success: {
self.tblInvalidServices.reloadData()
self.tblInvalidServices.mj_footer.endRefreshing()
}) { (error) in
self.tblInvalidServices.mj_footer.endRefreshing()
self.view.showToast(error, position: .bottom, popTime: 2.0, dismissOnTap: false )
}
} else {
service.getServices(serviceStatus: .COMPLETE, success: {
self.tblInvalidServices.reloadData()
self.tblInvalidServices.mj_header.endRefreshing()
}) { (errorMessage) in
self.tblInvalidServices.mj_header.endRefreshing()
self.view.showToast(errorMessage, position: .bottom, popTime: 2.0, dismissOnTap: false)
}
}
}
}
extension InvalidServicesViewController: UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return service.services.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let service_ = service.services[indexPath.row]
let cell = tableView.dequeueReusableCell(withIdentifier: "invalidServiceCell", for: indexPath) as! InvalidServicesCell
cell.lblCustomerName.text = service_.customerName
cell.lblService.text = service_.productName
cell.lblDate.text = service_.customerDate
if let profileImage = service_.customerProfileImage {
let url = URL(string: profileImage)
cell.imgCustomer.kf.setImage(with: url, placeholder: UIImage(named: "placeholder"), options: [.transition(ImageTransition.fade(1)), .scaleFactor(1.0)], progressBlock: nil, completionHandler: nil)
}
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
let selectedService = service.services[indexPath.row]
if selectedService.serviceType == .BOOKING {
let appointmentdetails = Utils.viewController(storyboardName: "Appointment", ofType: ServiceDetailViewController.self)
appointmentdetails.serviceStatus = .completed
appointmentdetails.incrementId = selectedService.incrementId
appointmentdetails.orderId = selectedService.orderId
self.navigationController?.pushViewController(appointmentdetails, animated: true)
} else if selectedService.serviceType == .FAQ {
if selectedService.status == .PROCESSING {
let chatViewController = Utils.viewController(storyboardName: "Home", ofType: OnlineEnquiryViewController.self)
if let orderId = selectedService.orderId {
chatViewController.orderId = orderId
self.navigationController?.pushViewController(chatViewController, animated: true)
}
} else {
let enquiryDetailsViewController = Utils.viewController(storyboardName: "Home", ofType: EnquiryDetailsViewController.self)
enquiryDetailsViewController.incrementId = selectedService.incrementId!
enquiryDetailsViewController.orderId = selectedService.orderId!
self.navigationController?.pushViewController(enquiryDetailsViewController, animated: true)
}
} else if selectedService.serviceType == .FORM{
if let form = Form.VC(with: selectedService.incrementId!, orderId: selectedService.orderId!, categoryType: selectedService.categoryType){
self.navigationController?.pushViewController(form, animated: true)
}
} else {
self.view.showToast("Unknown service", position: .bottom, popTime: 2.0, dismissOnTap: false)
}
// let serviceDetail = Utils.viewController(storyboardName: "Appointment", ofType: ServiceDetailViewController.self)
// serviceDetail.serviceStatus = .completed
// serviceDetail.incrementId = service.services[indexPath.row].incrementId
// serviceDetail.orderId = service.services[indexPath.row].orderId
// self.navigationController?.pushViewController(serviceDetail, animated: true)
}
}
class InvalidServicesCell: UITableViewCell {
@IBOutlet var imgCustomer: UIImageView!
@IBOutlet var lblCustomerName: UILabel!
@IBOutlet var lblService: UILabel!
@IBOutlet var lblDate: UILabel!
}
......@@ -12,7 +12,7 @@ import WMPageController
enum PMenuEnquiryTitle: String {
case ongoing = "Ongoing"
case complet = "Complete"
case processing = "Processing"
case processing = "Upcoming"
case invalid = "Invalid"
}
......@@ -26,7 +26,7 @@ class MoreServicesViewController: WMPageController {
// var userProfileHeaderView: UIViewController!
var panGesture: WMPanGestureRecognizer!
var lastPoint = CGPoint.zero
var vcTitles = ["Processing", "Ongoing", "Invalid", "Complete"]
var vcTitles = ["Upcoming", "Ongoing", "Invalid", "Complete"]
var viewTop: CGFloat = 5 {
didSet {
......@@ -152,12 +152,13 @@ class MoreServicesViewController: WMPageController {
case 1:
return Utils.viewController(storyboardName: "Appointment", ofType: OnGoingServicesViewController.self)
case 2:
return UIViewController()
return Utils.viewController(storyboardName: "Appointment", ofType: InvalidServicesViewController.self)
case 3:
return Utils.viewController(storyboardName: "Appointment", ofType: CompletedServicesViewController.self)
default:
return UIViewController()
}
}
// override func menuView(_ menu: WMMenuView!, titleSizeFor state: WMMenuItemState, at index: Int) -> CGFloat {
......
......@@ -65,7 +65,10 @@ extension VideoCallingViewController {
self.leaveChannel()
UpdateOrder.toServiceUpdloadResult(orderId: self.orderId, success: {
self.navigationController?.popViewController(animated: true)
let editResultViewController = Utils.viewController(storyboardName: "Appointment", ofType: EditResultViewController.self)
self.navigationController?.pushViewController(editResultViewController, animated: true)
}) { (errorMessage) in
self.navigationController?.popToRootViewController(animated: true)
self.view.showToast(errorMessage, position: .bottom, popTime: 2.0, dismissOnTap: false)
......
......@@ -272,6 +272,8 @@ class RecentServicesCell: UITableViewCell {
self.imgStatus.image = UIImage(named: "status-inprogress")
case .SERVICE_UPLOAD_RESULT:
self.imgStatus.image = UIImage(named: "status-inprogress")
case .ONGOING:
self.imgStatus.image = UIImage(named: "status-inprogress")
}
}
......
......@@ -135,6 +135,7 @@ extension OnlineEnquiryViewController {
}
@IBAction func startRecordAudio() {
Audio.startRecording(success: {
print("recording started")
}) { (errorMessage) in
......@@ -145,16 +146,36 @@ extension OnlineEnquiryViewController {
@IBAction func endRecordAudio() {
Audio.stopRecording(success: { (filePath) in
UploadAudio.upload(file: filePath, success: { (webPath) in
var fileSize : UInt64 = 1
do {
//return [FileAttributeKey : Any]
let attr = try FileManager.default.attributesOfItem(atPath: filePath)
fileSize = attr[FileAttributeKey.size] as! UInt64
//if you convert to NSDictionary, you can get file size old way as well.
let dict = attr as NSDictionary
fileSize = dict.fileSize()
} catch {
print("Error: \(error)")
}
let fileSizeInMB = fileSize/(1024*1024)
if fileSizeInMB >= 10 {
self.view.showToast("File size too large", position: .bottom, popTime: 2.0, dismissOnTap: false)
return
}
UploadAudio.upload(file: filePath, success: { (webPath) in
self.ref.childByAutoId().setValue(["msg": webPath,
"name": "Seller",
"status": "unread",
"chatType": "Audio",
"timestamp": ServerValue.timestamp()
])
}, failur: { (errorMessage) in
self.view.showToast(errorMessage, position: .bottom, popTime: 2.0, dismissOnTap: false)
})
......
......@@ -13,6 +13,7 @@ enum SellerOrderStatus: String {
case CLOSED
case SERVICE_COMPLETE
case SERVICE_ONGOING
case ONGOING
case COMPLETE
case SERVICE_UPLOAD_RESULT
case PENDING
......
......@@ -17,6 +17,7 @@ enum MyServiceStatus: String {
case CANCELED
case PENDING
case PROCESSING
case ONGOING
}
class Service {
......
......@@ -14,6 +14,7 @@ class Audio {
var audioRecorder:AVAudioRecorder!
var audioPlayer: AVPlayer?
var audioPlayerForLocalFile: AVAudioPlayer?
var currentRecordingPath: String?
static let shared = Audio()
......@@ -30,33 +31,27 @@ class Audio {
class func startRecording(success: () -> Void, failure: (_ message: String) -> Void) {
let audioSession:AVAudioSession = AVAudioSession.sharedInstance()
do {
try audioSession.setCategory(AVAudioSessionCategoryRecord)
try audioSession.setActive(true)
Audio.shared.currentRecordingPath = NSTemporaryDirectory() + "\(Audio.shared.Timestamp).m4a"
let url = URL(fileURLWithPath: Audio.shared.currentRecordingPath!)
let recordSettings: [String: Any] = [AVFormatIDKey: kAudioFormatMPEG4AAC,
AVSampleRateKey: 12000.0,
AVNumberOfChannelsKey: 1,
AVEncoderAudioQualityKey: AVAudioQuality.high.rawValue
]
Audio.shared.audioRecorder = try AVAudioRecorder(url: url, settings: recordSettings)
Audio.shared.audioRecorder.record()
success()
} catch {
failure(error.localizedDescription)
}
try! audioSession.setCategory(AVAudioSessionCategoryRecord)
try! audioSession.setActive(true)
Audio.shared.currentRecordingPath = NSTemporaryDirectory() + "\(Audio.shared.Timestamp).m4a"
let url = URL(fileURLWithPath: Audio.shared.currentRecordingPath!)
let recordSettings: [String: Any] = [AVFormatIDKey: kAudioFormatMPEG4AAC,
AVSampleRateKey: 12000.0,
AVNumberOfChannelsKey: 1,
AVEncoderAudioQualityKey: AVAudioQuality.high.rawValue
]
Audio.shared.audioRecorder = try! AVAudioRecorder(url: url, settings: recordSettings)
Audio.shared.audioRecorder.record()
}
class func stopRecording(success: (_ filePath: String) -> Void, failure: (_ message: String) -> Void) {
Audio.shared.audioRecorder.stop()
if let path = Audio.shared.currentRecordingPath {
success(path)
}
......@@ -64,36 +59,34 @@ class Audio {
class func playAudio(audioPath: String, success:() -> Void, failure: (_ message: String) -> Void) {
// let audioURL = URL(fileURLWithPath: audioPath)
do {
if audioPath.first == "/" { // local file path
let audioURL = URL(fileURLWithPath: audioPath)
do {
try AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayback)
try AVAudioSession.sharedInstance().setActive(true)
try Audio.shared.audioPlayerForLocalFile = AVAudioPlayer(contentsOf: audioURL)
Audio.shared.audioPlayerForLocalFile!.prepareToPlay()
Audio.shared.audioPlayerForLocalFile!.play()
success()
} catch {
failure(error.localizedDescription)
}
} else { // remote path
let url = URL(string: audioPath)
// let playerItem:AVPlayerItem = AVPlayerItem(url: url!)
// Audio.shared.audioPlayer = AVPlayer(playerItem: playerItem)
//
Audio.shared.audioPlayer = AVPlayer(url: url!)
Audio.shared.audioPlayer?.play()
// if Audio.shared.audioPlayer?.rate == 0 {
// Audio.shared.audioPlayer?.play()
// } else {
// Audio.shared.audioPlayer?.pause()
// }
// try Audio.shared.audioPlayer = AVAudioPlayer(contentsOf: audioURL)
// Audio.shared.audioPlayer!.prepareToPlay()
// Audio.shared.audioPlayer!.play()
success()
} catch {
failure(error.localizedDescription)
}
}
class func pause() {
if Audio.shared.audioPlayer != nil {
Audio.shared.audioPlayer!.pause()
Audio.shared.audioPlayerForLocalFile?.pause()
}
}
......
......@@ -102,8 +102,8 @@ class HTTP: NSObject {
}
} else {
User.regenerateToken { // generate magento token
if api == API.MAGENTO_TOKEN {
if api == API.REGENERATE_TOKEN {
User.regenerateToken { // generate magento token
if contentType == "application/x-www-form-urlencoded" {
HTTP().connectionWithRequestObjectFormUrl(api: api, parameters: parameters, method: method, indicator: indicator, success: success, failure: failure)
} else {
......@@ -111,6 +111,7 @@ class HTTP: NSObject {
}
}
}
}
} else {
......@@ -193,14 +194,17 @@ class HTTP: NSObject {
}
} else {
User.regenerateToken { // generate magento token
if contentType == "application/x-www-form-urlencoded" {
HTTP().connectionWithoutRequestObjectFormUrl(api: api, parameters: parameters, method: method, indicator: indicator, success: success, failure: failure)
} else {
HTTP().connectionWithoutRequestObject(api: api, parameters: parameters, method: method, indicator: indicator, success: success, failure: failure)
if api == API.REGENERATE_TOKEN {
User.regenerateToken { // generate magento token
if contentType == "application/x-www-form-urlencoded" {
HTTP().connectionWithoutRequestObjectFormUrl(api: api, parameters: parameters, method: method, indicator: indicator, success: success, failure: failure)
} else {
HTTP().connectionWithoutRequestObject(api: api, parameters: parameters, method: method, indicator: indicator, success: success, failure: failure)
}
}
}
}
} else {
if let result = response.result.value{
......
......@@ -22,5 +22,7 @@ target 'Bhagyashree' do
pod 'Firebase/Storage'
pod 'Firebase/Auth'
pod 'Firebase/Database'
pod 'Firebase/Messaging'
pod 'AgoraRtcEngine_iOS', '2.2.0'
pod 'INSPhotoGallery', '1.2.5'
end
......@@ -16,6 +16,9 @@ PODS:
- Firebase/Database (5.0.0):
- Firebase/CoreOnly
- FirebaseDatabase (= 5.0.0)
- Firebase/Messaging (5.0.0):
- Firebase/CoreOnly
- FirebaseMessaging (= 3.0.0)
- Firebase/Storage (5.0.0):
- Firebase/CoreOnly
- FirebaseStorage (= 3.0.0)
......@@ -34,10 +37,17 @@ PODS:
- leveldb-library (~> 1.18)
- FirebaseInstanceID (3.0.0):
- FirebaseCore (~> 5.0)
- FirebaseMessaging (3.0.0):
- FirebaseCore (~> 5.0)
- FirebaseInstanceID (~> 3.0)
- GoogleToolboxForMac/Logger (~> 2.1)
- Protobuf (~> 3.1)
- FirebaseStorage (3.0.0):
- FirebaseCore (~> 5.0)
- GTMSessionFetcher/Core (~> 1.1)
- GoogleToolboxForMac/Defines (2.1.4)
- GoogleToolboxForMac/Logger (2.1.4):
- GoogleToolboxForMac/Defines (= 2.1.4)
- "GoogleToolboxForMac/NSData+zlib (2.1.4)":
- GoogleToolboxForMac/Defines (= 2.1.4)
- GTMSessionFetcher/Core (1.1.15)
......@@ -47,6 +57,7 @@ PODS:
- ImageSlideshow/Kingfisher (1.5.3):
- ImageSlideshow/Core
- Kingfisher (> 3.0)
- INSPhotoGallery (1.2.5)
- IQKeyboardManagerSwift (5.0.8)
- Kingfisher (4.7.0)
- leveldb-library (1.20)
......@@ -57,6 +68,7 @@ PODS:
- nanopb/decode (0.3.8)
- nanopb/encode (0.3.8)
- Pastel (0.3.0)
- Protobuf (3.5.0)
- Realm (3.3.0):
- Realm/Headers (= 3.3.0)
- Realm/Headers (3.3.0)
......@@ -76,9 +88,11 @@ DEPENDENCIES:
- Firebase/Auth
- Firebase/Core
- Firebase/Database
- Firebase/Messaging
- Firebase/Storage
- ImageSlideshow
- ImageSlideshow/Kingfisher
- INSPhotoGallery (= 1.2.5)
- IQKeyboardManagerSwift (= 5.0.8)
- Kingfisher (= 4.7.0)
- MJRefresh (= 3.1.14.1)
......@@ -102,16 +116,19 @@ SPEC REPOS:
- FirebaseCore
- FirebaseDatabase
- FirebaseInstanceID
- FirebaseMessaging
- FirebaseStorage
- GoogleToolboxForMac
- GTMSessionFetcher
- ImageSlideshow
- INSPhotoGallery
- IQKeyboardManagerSwift
- Kingfisher
- leveldb-library
- MJRefresh
- nanopb
- Pastel
- Protobuf
- Realm
- RealmSwift
- SVProgressHUD
......@@ -131,16 +148,19 @@ SPEC CHECKSUMS:
FirebaseCore: e46e4babb9de298fb2f736958edcc6da1dc60d73
FirebaseDatabase: 697eb53e5b4fe7cd4fa8756c1f82a9fca011345f
FirebaseInstanceID: 83e0040351565df711a5db3d8ebe5ea21aca998a
FirebaseMessaging: f2360a966ecfb0d14facf0fbdf306efc2df0ddbe
FirebaseStorage: 7ca4bb7b58a25fa647b04f524033fc7cb7eb272b
GoogleToolboxForMac: 91c824d21e85b31c2aae9bb011c5027c9b4e738f
GTMSessionFetcher: 5fa5b80fd20e439ef5f545fb2cb3ca6c6714caa2
ImageSlideshow: 24a1184909f589379ba2527ebcc77996881bb1f2
INSPhotoGallery: 8bd5b434e70d06dd698085f8e865f6602d82014b
IQKeyboardManagerSwift: 2e7dc7f98c111458c1ea2b373f893e8cf95e2b97
Kingfisher: da6b005aa96d37698e3e4f1ccfe96a5b9bbf27d6
leveldb-library: 08cba283675b7ed2d99629a4bc5fd052cd2bb6a5
MJRefresh: 238e6a37e2dba12160ee3b79f6d2a2b26abcab42
nanopb: 5601e6bca2dbf1ed831b519092ec110f66982ca3
Pastel: 738740c84f2b15bbcd96ebe99c4e6c2f5dd42262
Protobuf: 8a9838fba8dae3389230e1b7f8c104aa32389c03
Realm: 7d74ad6c5020432f58fe5e1df90bb6d97b2c7fef
RealmSwift: b2fa31a09c22e246026766557405505eeaad2b01
SVProgressHUD: b0830714205bea1317ea1a2ebc71e5633af334d4
......@@ -148,6 +168,6 @@ SPEC CHECKSUMS:
TimeAgoInWords: 633dbb30810de855333dedd1d5033d28b1ecfd6f
WMPageController: 9f219bb8912a1a1f51af11fde61e2682a7b7e7f2
PODFILE CHECKSUM: b376ed8a9c64f69a0b9ee9bf8ad4ed33a5b0118d
PODFILE CHECKSUM: c0ec16ebb21736417bd47618790d9417fe8f19e1
COCOAPODS: 1.5.3
/*
* Copyright 2017 Google
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#import "FIRMessaging.h"
/**
* This category extends FIRMessaging with the configuration for using Cloud Messaging.
*/
@interface FIRMessaging (FIRApp)
@end
/*
* Copyright 2017 Google
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#import "FIRMessaging+FIRApp.h"
#import <FirebaseCore/FIRAppInternal.h>
#import <FirebaseCore/FIROptionsInternal.h>
#import "FIRMessagingConstants.h"
#import "FIRMessagingLogger.h"
#import "FIRMessagingPubSub.h"
#import "FIRMessagingRemoteNotificationsProxy.h"
#import "FIRMessagingVersionUtilities.h"
#import "FIRMessaging_Private.h"
@interface FIRMessaging ()
@property(nonatomic, readwrite, strong) NSString *fcmSenderID;
@end
@implementation FIRMessaging (FIRApp)
+ (void)load {
// FIRMessaging by default removes itself from observing any notifications.
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(didReceiveConfigureSDKNotification:)
name:kFIRAppReadyToConfigureSDKNotification
object:[FIRApp class]];
}
+ (void)didReceiveConfigureSDKNotification:(NSNotification *)notification {
NSDictionary *appInfoDict = notification.userInfo;
NSNumber *isDefaultApp = appInfoDict[kFIRAppIsDefaultAppKey];
if (![isDefaultApp boolValue]) {
// Only configure for the default FIRApp.
FIRMessagingLoggerDebug(kFIRMessagingMessageCodeFIRApp001,
@"Firebase Messaging only works with the default app.");
return;
}
NSString *appName = appInfoDict[kFIRAppNameKey];
FIRApp *app = [FIRApp appNamed:appName];
[[FIRMessaging messaging] configureMessaging:app];
}
- (void)configureMessaging:(FIRApp *)app {
FIROptions *options = app.options;
NSError *error;
if (!options.GCMSenderID.length) {
error =
[FIRApp errorForSubspecConfigurationFailureWithDomain:kFirebaseCloudMessagingErrorDomain
errorCode:FIRErrorCodeCloudMessagingFailed
service:kFIRServiceMessaging
reason:@"Google Sender ID must not be nil"
@" or empty."];
[self exitApp:app withError:error];
return;
}
self.fcmSenderID = [options.GCMSenderID copy];
// Swizzle remote-notification-related methods (app delegate and UNUserNotificationCenter)
if ([FIRMessagingRemoteNotificationsProxy canSwizzleMethods]) {
NSString *docsURLString = @"https://firebase.google.com/docs/cloud-messaging/ios/client"
@"#method_swizzling_in_firebase_messaging";
FIRMessagingLoggerNotice(kFIRMessagingMessageCodeFIRApp000,
@"FIRMessaging Remote Notifications proxy enabled, will swizzle "
@"remote notification receiver handlers. If you'd prefer to manually "
@"integrate Firebase Messaging, add \"%@\" to your Info.plist, "
@"and set it to NO. Follow the instructions at:\n%@\nto ensure "
@"proper integration.",
kFIRMessagingRemoteNotificationsProxyEnabledInfoPlistKey,
docsURLString);
[FIRMessagingRemoteNotificationsProxy swizzleMethods];
}
}
- (void)exitApp:(FIRApp *)app withError:(NSError *)error {
[app sendLogsWithServiceName:kFIRServiceMessaging
version:FIRMessagingCurrentLibraryVersion()
error:error];
if (error) {
NSString *message = nil;
if (app.options.usingOptionsFromDefaultPlist) {
// Configured using plist file
message = [NSString stringWithFormat:@"Firebase Messaging has stopped your project because "
@"there are missing or incorrect values provided in %@.%@ that may prevent "
@"your app from behaving as expected:\n\n"
@"Error: %@\n\n"
@"Please fix these issues to ensure that Firebase is correctly configured in "
@"your project.",
kServiceInfoFileName,
kServiceInfoFileType,
error.localizedFailureReason];
} else {
// Configured manually
message = [NSString stringWithFormat:@"Firebase Messaging has stopped your project because "
@"there are missing or incorrect values in Firebase's configuration options "
@"that may prevent your app from behaving as expected:\n\n"
@"Error:%@\n\n"
@"Please fix these issues to ensure that Firebase is correctly configured in "
@"your project.",
error.localizedFailureReason];
}
[NSException raise:kFirebaseCloudMessagingErrorDomain format:@"%@", message];
}
}
@end
/*
* Copyright 2017 Google
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#import <Foundation/Foundation.h>
/**
* Register the device with Checkin Service and get back the `authID`, `secret token` etc. for the
* client. Checkin results are cached in the `FIRMessagingDefaultsManager` and periodically refreshed to
* prevent them from being stale. Each client needs to register with checkin before registering
* with FIRMessaging.
*/
@interface FIRMessagingCheckinService : NSObject
@property(nonatomic, readonly, strong) NSString *deviceAuthID;
@property(nonatomic, readonly, strong) NSString *secretToken;
@property(nonatomic, readonly, strong) NSString *versionInfo;
@property(nonatomic, readonly, assign) BOOL hasValidCheckinInfo;
/**
* Verify if valid checkin preferences have been loaded in memory.
*
* @return YES if valid checkin preferences exist in memory else NO.
*/
- (BOOL)hasValidCheckinInfo;
/**
* Try to load prefetched checkin preferences from the cache. This supports the use case where
* InstanceID library has already obtained a valid checkin and we should be using that.
*
* This should be used as a last gasp effort to retreive any cached checkin preferences before
* hitting the FIRMessaging backend to retrieve new preferences.
*
* Note this is only required because InstanceID and FIRMessaging both require checkin preferences which
* need to be synced with each other.
*
* @return YES if successfully loaded cached checkin preferences into memory else NO.
*/
- (BOOL)tryToLoadPrefetchedCheckinPreferences;
@end
/*
* Copyright 2017 Google
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#import "FIRMessagingCheckinService.h"
#import "FIRMessagingUtilities.h"
#import "NSError+FIRMessaging.h"
@interface FIRMessagingCheckinService ()
// This property is of type FIRInstanceIDCheckinPreferences, if InstanceID was directly linkable
@property(nonatomic, readwrite, strong) id checkinPreferences;
@end
@implementation FIRMessagingCheckinService;
#pragma mark - Reflection-Based Getter Functions
// Encapsulates the -hasValidCheckinInfo method of FIRInstanceIDCheckinPreferences
BOOL FIRMessagingCheckinService_hasValidCheckinInfo(id checkinPreferences) {
SEL hasValidCheckinInfoSelector = NSSelectorFromString(@"hasValidCheckinInfo");
if (![checkinPreferences respondsToSelector:hasValidCheckinInfoSelector]) {
// Can't check hasValidCheckinInfo
return NO;
}
// Since hasValidCheckinInfo returns a BOOL, use NSInvocation
NSMethodSignature *methodSignature =
[[checkinPreferences class] instanceMethodSignatureForSelector:hasValidCheckinInfoSelector];
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:methodSignature];
invocation.selector = hasValidCheckinInfoSelector;
invocation.target = checkinPreferences;
[invocation invoke];
BOOL returnValue;
[invocation getReturnValue:&returnValue];
return returnValue;
}
// Returns a non-scalar (id) object based on the property name
id FIRMessagingCheckinService_propertyNamed(id checkinPreferences, NSString *propertyName) {
SEL propertyGetterSelector = NSSelectorFromString(propertyName);
if ([checkinPreferences respondsToSelector:propertyGetterSelector]) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
return [checkinPreferences performSelector:propertyGetterSelector];
#pragma clang diagnostic pop
}
return nil;
}
#pragma mark - Methods
- (BOOL)tryToLoadPrefetchedCheckinPreferences {
Class instanceIDClass = NSClassFromString(@"FIRInstanceID");
if (!instanceIDClass) {
// InstanceID is not linked
return NO;
}
// [FIRInstanceID instanceID]
SEL instanceIDSelector = NSSelectorFromString(@"instanceID");
if (![instanceIDClass respondsToSelector:instanceIDSelector]) {
return NO;
}
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
id instanceID = [instanceIDClass performSelector:instanceIDSelector];
#pragma clang diagnostic pop
if (!instanceID) {
// Instance ID singleton not available
return NO;
}
// [[FIRInstanceID instanceID] cachedCheckinPreferences]
SEL cachedCheckinPrefsSelector = NSSelectorFromString(@"cachedCheckinPreferences");
if (![instanceID respondsToSelector:cachedCheckinPrefsSelector]) {
// cachedCheckinPreferences is not accessible
return NO;
}
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
id checkinPreferences = [instanceID performSelector:cachedCheckinPrefsSelector];
#pragma clang diagnostic pop
if (!checkinPreferences) {
// No cached checkin prefs
return NO;
}
BOOL hasValidInfo = FIRMessagingCheckinService_hasValidCheckinInfo(checkinPreferences);
if (hasValidInfo) {
self.checkinPreferences = checkinPreferences;
}
return hasValidInfo;
}
#pragma mark - API
- (NSString *)deviceAuthID {
return FIRMessagingCheckinService_propertyNamed(self.checkinPreferences, @"deviceID");
}
- (NSString *)secretToken {
return FIRMessagingCheckinService_propertyNamed(self.checkinPreferences, @"secretToken");
}
- (NSString *)versionInfo {
return FIRMessagingCheckinService_propertyNamed(self.checkinPreferences, @"versionInfo");
}
- (NSString *)digest {
return FIRMessagingCheckinService_propertyNamed(self.checkinPreferences, @"digest");
}
- (BOOL)hasValidCheckinInfo {
return FIRMessagingCheckinService_hasValidCheckinInfo(self.checkinPreferences);
}
@end
/*
* Copyright 2017 Google
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#import "FIRMessaging.h"
@class FIRReachabilityChecker;
@class GPBMessage;
@class FIRMessagingConnection;
@class FIRMessagingDataMessageManager;
@class FIRMessagingRmqManager;
/**
* Callback to handle MCS connection requests.
*
* @param error The error object if any while trying to connect with MCS else nil.
*/
typedef void(^FIRMessagingConnectCompletionHandler)(NSError *error);
@protocol FIRMessagingClientDelegate <NSObject>
@end
/**
* The client handles the subscribe/unsubscribe for an unregistered senderID
* and device. It also manages the FIRMessaging data connection, the exponential backoff
* algorithm in case of registration failures, sign in failures and unregister
* failures. It also handles the reconnect logic if the FIRMessaging connection is
* broken off by some error during an active session.
*/
@interface FIRMessagingClient : NSObject
@property(nonatomic, readonly, strong) FIRMessagingConnection *connection;
@property(nonatomic, readwrite, weak) FIRMessagingDataMessageManager *dataMessageManager;
// Designated initializer
- (instancetype)initWithDelegate:(id<FIRMessagingClientDelegate>)delegate
reachability:(FIRReachabilityChecker *)reachability
rmq2Manager:(FIRMessagingRmqManager *)rmq2Manager;
- (void)teardown;
- (void)cancelAllRequests;
#pragma mark - FIRMessaging subscribe
/**
* Update the subscription associated with the given token and topic.
*
* For a to-be-created subscription we check if the client is already
* subscribed to the topic or not. If subscribed we should have the
* subscriptionID in the cache and we return from there itself, else we call
* the FIRMessaging backend to create a new subscription for the topic for this client.
*
* For delete subscription requests we delete the stored subscription in the
* client and then invoke the FIRMessaging backend to delete the existing subscription
* completely.
*
* @param token The token associated with the device.
* @param topic The topic for which the subscription should be updated.
* @param options The options to be passed in to the subscription request.
* @param shouldDelete If YES this would delete the subscription from the cache
* and also let the FIRMessaging backend know that we need to delete
* the subscriptionID associated with this topic.
* If NO we try to create a new subscription for the given
* token and topic.
* @param handler The handler to invoke once the subscription request
* finishes.
*/
- (void)updateSubscriptionWithToken:(NSString *)token
topic:(NSString *)topic
options:(NSDictionary *)options
shouldDelete:(BOOL)shouldDelete
handler:(FIRMessagingTopicOperationCompletion)handler;
#pragma mark - MCS Connection
/**
* Create a MCS connection.
*
* @param handler The handler to be invokend once the connection is setup. If
* setting up the connection fails we invoke the handler with
* an appropriate error object.
*/
- (void)connectWithHandler:(FIRMessagingConnectCompletionHandler)handler;
/**
* Disconnect the current MCS connection. If there is no valid connection this
* should be a NO-OP.
*/
- (void)disconnect;
#pragma mark - MCS Connection State
/**
* If we are connected to MCS or not. This doesn't take into account the fact if
* the client has been signed in(verified) by MCS.
*
* @return YES if we are signed in or connecting and trying to sign-in else NO.
*/
@property(nonatomic, readonly) BOOL isConnected;
/**
* If we have an active MCS connection
*
* @return YES if we have an active MCS connection else NO.
*/
@property(nonatomic, readonly) BOOL isConnectionActive;
/**
* If we should be connected to MCS
*
* @return YES if we have attempted a connection and not requested to disconect.
*/
@property(nonatomic, readonly) BOOL shouldStayConnected;
/**
* Schedule a retry to connect to MCS. If `immediately` is `YES` try to
* schedule a retry now else retry with some delay.
*
* @param immediately Should retry right now.
*/
- (void)retryConnectionImmediately:(BOOL)immediately;
#pragma mark - Messages
/**
* Send a message over the MCS connection.
*
* @param message Message to be sent.
*/
- (void)sendMessage:(GPBMessage *)message;
/**
* Send message if we have an active MCS connection. If not cache the message
* for this session and in case we are able to re-establish the connection try
* again else drop it. This should only be used for TTL=0 messages for now.
*
* @param message Message to be sent.
*/
- (void)sendOnConnectOrDrop:(GPBMessage *)message;
@end
/*
* Copyright 2017 Google
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#import <Foundation/Foundation.h>
@interface FIRMessagingCodedInputStream : NSObject
@property(nonatomic, readonly, assign) size_t offset;
- (instancetype)initWithData:(NSData *)data;
- (BOOL)readTag:(int8_t *)tag;
- (BOOL)readLength:(int32_t *)length;
- (NSData *)readDataWithLength:(uint32_t)length;
@end
/*
* Copyright 2017 Google
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#import "FIRMessagingCodedInputStream.h"
#import "FIRMessagingDefines.h"
typedef struct {
const void *bytes;
size_t bufferSize;
size_t bufferPos;
} BufferState;
static BOOL CheckSize(BufferState *state, size_t size) {
size_t newSize = state->bufferPos + size;
if (newSize > state->bufferSize) {
return NO;
}
return YES;
}
static BOOL ReadRawByte(BufferState *state, int8_t *output) {
_FIRMessagingDevAssert(output != NULL && state != NULL, @"Invalid parameters");
if (CheckSize(state, sizeof(int8_t))) {
*output = ((int8_t *)state->bytes)[state->bufferPos++];
return YES;
}
return NO;
}
static BOOL ReadRawVarInt32(BufferState *state, int32_t *output) {
_FIRMessagingDevAssert(output != NULL && state != NULL, @"Invalid parameters");
int8_t tmp = 0;
if (!ReadRawByte(state, &tmp)) {
return NO;
}
if (tmp >= 0) {
*output = tmp;
return YES;
}
int32_t result = tmp & 0x7f;
if (!ReadRawByte(state, &tmp)) {
return NO;
}
if (tmp >= 0) {
result |= tmp << 7;
} else {
result |= (tmp & 0x7f) << 7;
if (!ReadRawByte(state, &tmp)) {
return NO;
}
if (tmp >= 0) {
result |= tmp << 14;
} else {
result |= (tmp & 0x7f) << 14;
if (!ReadRawByte(state, &tmp)) {
return NO;
}
if (tmp >= 0) {
result |= tmp << 21;
} else {
result |= (tmp & 0x7f) << 21;
if (!ReadRawByte(state, &tmp)) {
return NO;
}
result |= tmp << 28;
if (tmp < 0) {
// Discard upper 32 bits.
for (int i = 0; i < 5; ++i) {
if (!ReadRawByte(state, &tmp)) {
return NO;
}
if (tmp >= 0) {
*output = result;
return YES;
}
}
return NO;
}
}
}
}
*output = result;
return YES;
}
@interface FIRMessagingCodedInputStream()
@property(nonatomic, readwrite, strong) NSData *buffer;
@property(nonatomic, readwrite, assign) BufferState state;
@end
@implementation FIRMessagingCodedInputStream;
- (instancetype)initWithData:(NSData *)data {
self = [super init];
if (self) {
_buffer = data;
_state.bytes = _buffer.bytes;
_state.bufferSize = _buffer.length;
}
return self;
}
- (size_t)offset {
return _state.bufferPos;
}
- (BOOL)readTag:(int8_t *)tag {
return ReadRawByte(&_state, tag);
}
- (BOOL)readLength:(int32_t *)length {
return ReadRawVarInt32(&_state, length);
}
- (NSData *)readDataWithLength:(uint32_t)length {
if (!CheckSize(&_state, length)) {
return nil;
}
const void *bytesToRead = _state.bytes + _state.bufferPos;
NSData *result = [NSData dataWithBytes:bytesToRead length:length];
_state.bufferPos += length;
return result;
}
@end
/*
* Copyright 2017 Google
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#import <Foundation/Foundation.h>
@class FIRMessagingConnection;
@class FIRMessagingDataMessageManager;
@class FIRMessagingRmqManager;
@class GtalkDataMessageStanza;
@class GPBMessage;
typedef void (^FIRMessagingMessageHandler)(NSDictionary *);
typedef NS_ENUM(NSUInteger, FIRMessagingConnectionState) {
kFIRMessagingConnectionNotConnected = 0,
kFIRMessagingConnectionConnecting,
kFIRMessagingConnectionConnected,
kFIRMessagingConnectionSignedIn,
};
typedef NS_ENUM(NSUInteger, FIRMessagingConnectionCloseReason) {
kFIRMessagingConnectionCloseReasonSocketDisconnected = 0,
kFIRMessagingConnectionCloseReasonTimeout,
kFIRMessagingConnectionCloseReasonUserDisconnect,
};
@protocol FIRMessagingConnectionDelegate<NSObject>
- (void)connection:(FIRMessagingConnection *)fcmConnection
didCloseForReason:(FIRMessagingConnectionCloseReason)reason;
- (void)didLoginWithConnection:(FIRMessagingConnection *)fcmConnection;
- (void)connectionDidRecieveMessage:(GtalkDataMessageStanza *)message;
/**
* Called when a stream ACK or a selective ACK are received - this indicates the
* message has been received by MCS.
* @return The count of rmqIds deleted from the client RMQ store.
*/
- (int)connectionDidReceiveAckForRmqIds:(NSArray *)rmqIds;
@end
/**
* This class maintains the actual FIRMessaging connection that we use to receive and send messages
* while the app is in foreground. Once we have a registrationID from the FIRMessaging backend we
* are able to set up this connection which is used for any further communication with FIRMessaging
* backend. In case the connection breaks off while the app is still being used we try to rebuild
* the connection with an exponential backoff.
*
* This class also notifies the delegate about the main events happening in the lifcycle of the
* FIRMessaging connection (read FIRMessagingConnectionDelegate). All of the `on-the-wire`
* interactions with FIRMessaging are channelled through here.
*/
@interface FIRMessagingConnection : NSObject
@property(nonatomic, readwrite, assign) int64_t lastHeartbeatPingTimestamp;
@property(nonatomic, readonly, assign) FIRMessagingConnectionState state;
@property(nonatomic, readonly, copy) NSString *host;
@property(nonatomic, readonly, assign) NSUInteger port;
@property(nonatomic, readwrite, weak) id<FIRMessagingConnectionDelegate> delegate;
- (instancetype)initWithAuthID:(NSString *)authId
token:(NSString *)token
host:(NSString *)host
port:(NSUInteger)port
runLoop:(NSRunLoop *)runLoop
rmq2Manager:(FIRMessagingRmqManager *)rmq2Manager
fcmManager:(FIRMessagingDataMessageManager *)dataMessageManager;
- (void)signIn; // connect
- (void)signOut; // disconnect
/**
* Teardown the FIRMessaging connection and deallocate the resources being held up by the
* connection.
*/
- (void)teardown;
/**
* Send proto to the wire. The message will be cached before we try to send so that in case of
* failure we can send it again later on when we have connection.
*/
- (void)sendProto:(GPBMessage *)proto;
/**
* Send a message after the currently in progress connection succeeds, otherwise drop it.
*
* This should be used for TTL=0 messages that force a reconnect. They shouldn't be persisted
* in the RMQ, but they should be sent if the reconnect is successful.
*/
- (void)sendOnConnectOrDrop:(GPBMessage *)message;
@end
/*
* Copyright 2017 Google
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* Global constants to be put here.
*
*/
#import <Foundation/Foundation.h>
#ifndef _FIRMessaging_CONSTANTS_H
#define _FIRMessaging_CONSTANTS_H
FOUNDATION_EXPORT NSString *const kFIRMessagingRawDataKey;
FOUNDATION_EXPORT NSString *const kFIRMessagingCollapseKey;
FOUNDATION_EXPORT NSString *const kFIRMessagingFromKey;
FOUNDATION_EXPORT NSString *const kFIRMessagingSendTo;
FOUNDATION_EXPORT NSString *const kFIRMessagingSendTTL;
FOUNDATION_EXPORT NSString *const kFIRMessagingSendDelay;
FOUNDATION_EXPORT NSString *const kFIRMessagingSendMessageID;
FOUNDATION_EXPORT NSString *const KFIRMessagingSendMessageAppData;
FOUNDATION_EXPORT NSString *const kFIRMessagingMessageInternalReservedKeyword;
FOUNDATION_EXPORT NSString *const kFIRMessagingMessagePersistentIDKey;
FOUNDATION_EXPORT NSString *const kFIRMessagingMessageIDKey;
FOUNDATION_EXPORT NSString *const kFIRMessagingMessageAPNSContentAvailableKey;
FOUNDATION_EXPORT NSString *const kFIRMessagingMessageSyncViaMCSKey;
FOUNDATION_EXPORT NSString *const kFIRMessagingMessageSyncMessageTTLKey;
FOUNDATION_EXPORT NSString *const kFIRMessagingMessageLinkKey;
FOUNDATION_EXPORT NSString *const kFIRMessagingRemoteNotificationsProxyEnabledInfoPlistKey;
FOUNDATION_EXPORT NSString *const kFIRMessagingApplicationSupportSubDirectory;
// Notifications
FOUNDATION_EXPORT NSString *const kFIRMessagingCheckinFetchedNotification;
FOUNDATION_EXPORT NSString *const kFIRMessagingAPNSTokenNotification;
FOUNDATION_EXPORT NSString *const kFIRMessagingFCMTokenNotification;
FOUNDATION_EXPORT NSString *const kFIRMessagingInstanceIDTokenRefreshNotification __deprecated_msg("Use kFIRMessagingRegistrationTokenRefreshNotification instead");
FOUNDATION_EXPORT NSString *const kFIRMessagingRegistrationTokenRefreshNotification;
FOUNDATION_EXPORT const int kFIRMessagingSendTtlDefault; // 24 hours
#endif
/*
* Copyright 2017 Google
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#import "FIRMessagingConstants.h"
NSString *const kFIRMessagingRawDataKey = @"rawData";
NSString *const kFIRMessagingCollapseKey = @"collapse_key";
NSString *const kFIRMessagingFromKey = @"from";
NSString *const kFIRMessagingSendTo = @"google." @"to";
NSString *const kFIRMessagingSendTTL = @"google." @"ttl";
NSString *const kFIRMessagingSendDelay = @"google." @"delay";
NSString *const kFIRMessagingSendMessageID = @"google." @"msg_id";
NSString *const KFIRMessagingSendMessageAppData = @"google." @"data";
NSString *const kFIRMessagingMessageInternalReservedKeyword = @"gcm.";
NSString *const kFIRMessagingMessagePersistentIDKey = @"persistent_id";
NSString *const kFIRMessagingMessageIDKey = @"gcm." @"message_id";
NSString *const kFIRMessagingMessageAPNSContentAvailableKey = @"content-available";
NSString *const kFIRMessagingMessageSyncViaMCSKey = @"gcm." @"duplex";
NSString *const kFIRMessagingMessageSyncMessageTTLKey = @"gcm." @"ttl";
NSString *const kFIRMessagingMessageLinkKey = @"gcm." @"app_link";
NSString *const kFIRMessagingRemoteNotificationsProxyEnabledInfoPlistKey =
@"FirebaseAppDelegateProxyEnabled";
NSString *const kFIRMessagingApplicationSupportSubDirectory = @"Google/FirebaseMessaging";
// Notifications
NSString *const kFIRMessagingCheckinFetchedNotification = @"com.google.gcm.notif-checkin-fetched";
NSString *const kFIRMessagingAPNSTokenNotification = @"com.firebase.iid.notif.apns-token";
NSString *const kFIRMessagingFCMTokenNotification = @"com.firebase.iid.notif.fcm-token";
NSString *const kFIRMessagingInstanceIDTokenRefreshNotification =
@"com.firebase.iid.notif.refresh-token";
NSString *const kFIRMessagingRegistrationTokenRefreshNotification =
@"com.firebase.iid.notif.refresh-token";
const int kFIRMessagingSendTtlDefault = 24 * 60 * 60; // 24 hours
/*
* Copyright 2017 Google
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#import <Foundation/Foundation.h>
FOUNDATION_EXPORT NSString *const kFIRMessagingContextManagerCategory;
FOUNDATION_EXPORT NSString *const kFIRMessagingContextManagerLocalTimeStart;
FOUNDATION_EXPORT NSString *const kFIRMessagingContextManagerLocalTimeEnd;
FOUNDATION_EXPORT NSString *const kFIRMessagingContextManagerBodyKey;
@interface FIRMessagingContextManagerService : NSObject
/**
* Check if the message is a context manager message or not.
*
* @param message The message to verify.
*
* @return YES if the message is a context manager message else NO.
*/
+ (BOOL)isContextManagerMessage:(NSDictionary *)message;
/**
* Handle context manager message.
*
* @param message The message to handle.
*
* @return YES if the message was handled successfully else NO.
*/
+ (BOOL)handleContextManagerMessage:(NSDictionary *)message;
@end
/*
* Copyright 2017 Google
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#import "FIRMessagingContextManagerService.h"
#import <UIKit/UIKit.h>
#import "FIRMessagingDefines.h"
#import "FIRMessagingLogger.h"
#define kFIRMessagingContextManagerPrefixKey @"google.c.cm."
#define kFIRMessagingContextManagerNotificationKeyPrefix @"gcm.notification."
static NSString *const kLogTag = @"FIRMessagingAnalytics";
static NSString *const kLocalTimeFormatString = @"yyyy-MM-dd HH:mm:ss";
static NSString *const kContextManagerPrefixKey = kFIRMessagingContextManagerPrefixKey;
// Local timed messages (format yyyy-mm-dd HH:mm:ss)
NSString *const kFIRMessagingContextManagerLocalTimeStart = kFIRMessagingContextManagerPrefixKey @"lt_start";
NSString *const kFIRMessagingContextManagerLocalTimeEnd = kFIRMessagingContextManagerPrefixKey @"lt_end";
// Local Notification Params
NSString *const kFIRMessagingContextManagerBodyKey = kFIRMessagingContextManagerNotificationKeyPrefix @"body";
NSString *const kFIRMessagingContextManagerTitleKey = kFIRMessagingContextManagerNotificationKeyPrefix @"title";
NSString *const kFIRMessagingContextManagerBadgeKey = kFIRMessagingContextManagerNotificationKeyPrefix @"badge";
NSString *const kFIRMessagingContextManagerCategoryKey =
kFIRMessagingContextManagerNotificationKeyPrefix @"click_action";
NSString *const kFIRMessagingContextManagerSoundKey = kFIRMessagingContextManagerNotificationKeyPrefix @"sound";
NSString *const kFIRMessagingContextManagerContentAvailableKey =
kFIRMessagingContextManagerNotificationKeyPrefix @"content-available";
typedef NS_ENUM(NSUInteger, FIRMessagingContextManagerMessageType) {
FIRMessagingContextManagerMessageTypeNone,
FIRMessagingContextManagerMessageTypeLocalTime,
};
@implementation FIRMessagingContextManagerService
+ (BOOL)isContextManagerMessage:(NSDictionary *)message {
// For now we only support local time in ContextManager.
if (![message[kFIRMessagingContextManagerLocalTimeStart] length]) {
FIRMessagingLoggerDebug(kFIRMessagingMessageCodeContextManagerService000,
@"Received message missing local start time, dropped.");
return NO;
}
return YES;
}
+ (BOOL)handleContextManagerMessage:(NSDictionary *)message {
NSString *startTimeString = message[kFIRMessagingContextManagerLocalTimeStart];
if (startTimeString.length) {
FIRMessagingLoggerDebug(kFIRMessagingMessageCodeContextManagerService001,
@"%@ Received context manager message with local time %@", kLogTag,
startTimeString);
return [self handleContextManagerLocalTimeMessage:message];
}
return NO;
}
+ (BOOL)handleContextManagerLocalTimeMessage:(NSDictionary *)message {
NSString *startTimeString = message[kFIRMessagingContextManagerLocalTimeStart];
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setDateFormat:kLocalTimeFormatString];
NSDate *startDate = [dateFormatter dateFromString:startTimeString];
_FIRMessagingDevAssert(startDate, @"Invalid local start date format %@", startTimeString);
if (!startTimeString) {
FIRMessagingLoggerError(kFIRMessagingMessageCodeContextManagerService002,
@"Invalid local start date format %@. Message dropped",
startTimeString);
return NO;
}
NSDate *currentDate = [NSDate date];
if ([currentDate compare:startDate] == NSOrderedAscending) {
[self scheduleLocalNotificationForMessage:message
atDate:startDate];
} else {
// check end time has not passed
NSString *endTimeString = message[kFIRMessagingContextManagerLocalTimeEnd];
if (!endTimeString) {
FIRMessagingLoggerInfo(
kFIRMessagingMessageCodeContextManagerService003,
@"No end date specified for message, start date elapsed. Message dropped.");
return YES;
}
NSDate *endDate = [dateFormatter dateFromString:endTimeString];
_FIRMessagingDevAssert(endDate, @"Invalid local end date format %@", endTimeString);
if (!endTimeString) {
FIRMessagingLoggerError(kFIRMessagingMessageCodeContextManagerService004,
@"Invalid local end date format %@. Message dropped", endTimeString);
return NO;
}
if ([endDate compare:currentDate] == NSOrderedAscending) {
// end date has already passed drop the message
FIRMessagingLoggerInfo(kFIRMessagingMessageCodeContextManagerService005,
@"End date %@ has already passed. Message dropped.", endTimeString);
return YES;
}
// schedule message right now (buffer 10s)
[self scheduleLocalNotificationForMessage:message
atDate:[currentDate dateByAddingTimeInterval:10]];
}
return YES;
}
+ (void)scheduleLocalNotificationForMessage:(NSDictionary *)message
atDate:(NSDate *)date {
NSDictionary *apsDictionary = message;
UILocalNotification *notification = [[UILocalNotification alloc] init];
// A great way to understand timezones and UILocalNotifications
// http://stackoverflow.com/questions/18424569/understanding-uilocalnotification-timezone
notification.timeZone = [NSTimeZone defaultTimeZone];
notification.fireDate = date;
// In the current solution all of the display stuff goes into a special "aps" dictionary
// being sent in the message.
if ([apsDictionary[kFIRMessagingContextManagerBodyKey] length]) {
notification.alertBody = apsDictionary[kFIRMessagingContextManagerBodyKey];
}
if ([apsDictionary[kFIRMessagingContextManagerTitleKey] length]) {
// |alertTitle| is iOS 8.2+, so check if we can set it
if ([notification respondsToSelector:@selector(setAlertTitle:)]) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunguarded-availability"
notification.alertTitle = apsDictionary[kFIRMessagingContextManagerTitleKey];
#pragma pop
}
}
if (apsDictionary[kFIRMessagingContextManagerSoundKey]) {
notification.soundName = apsDictionary[kFIRMessagingContextManagerSoundKey];
}
if (apsDictionary[kFIRMessagingContextManagerBadgeKey]) {
notification.applicationIconBadgeNumber =
[apsDictionary[kFIRMessagingContextManagerBadgeKey] integerValue];
}
if (apsDictionary[kFIRMessagingContextManagerCategoryKey]) {
// |category| is iOS 8.0+, so check if we can set it
if ([notification respondsToSelector:@selector(setCategory:)]) {
notification.category = apsDictionary[kFIRMessagingContextManagerCategoryKey];
}
}
NSDictionary *userInfo = [self parseDataFromMessage:message];
if (userInfo.count) {
notification.userInfo = userInfo;
}
[[UIApplication sharedApplication] scheduleLocalNotification:notification];
}
+ (NSDictionary *)parseDataFromMessage:(NSDictionary *)message {
NSMutableDictionary *data = [NSMutableDictionary dictionary];
for (NSObject<NSCopying> *key in message) {
if ([key isKindOfClass:[NSString class]]) {
NSString *keyString = (NSString *)key;
if ([keyString isEqualToString:kFIRMessagingContextManagerContentAvailableKey]) {
continue;
} else if ([keyString hasPrefix:kContextManagerPrefixKey]) {
continue;
}
}
data[[key copy]] = message[key];
}
return [data copy];
}
@end
/*
* Copyright 2017 Google
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#import <Foundation/Foundation.h>
@class GtalkDataMessageStanza;
@class FIRMessagingClient;
@class FIRMessagingConnection;
@class FIRMessagingReceiver;
@class FIRMessagingRmqManager;
@class FIRMessagingSyncMessageManager;
@protocol FIRMessagingDataMessageManagerDelegate <NSObject>
#pragma mark - Downstream Callbacks
/**
* Invoked when FIRMessaging receives a downstream message via the MCS connection.
* Let's the user know that they have received a new message by invoking the
* App's remoteNotification callback.
*
* @param message The downstream message received by the MCS connection.
*/
- (void)didReceiveMessage:(nonnull NSDictionary *)message
withIdentifier:(nullable NSString *)messageID;
#pragma mark - Upstream Callbacks
/**
* Notify the app that FIRMessaging will soon be sending the upstream message requested by the app.
*
* @param messageID The messageId passed in by the app to track this particular message.
* @param error The error in case FIRMessaging cannot send the message upstream.
*/
- (void)willSendDataMessageWithID:(nonnull NSString *)messageID error:(nullable NSError *)error;
/**
* Notify the app that FIRMessaging did successfully send it's message via the MCS
* connection and the message was successfully delivered.
*
* @param messageId The messageId passed in by the app to track this particular
* message.
*/
- (void)didSendDataMessageWithID:(nonnull NSString *)messageId;
#pragma mark - Server Callbacks
/**
* Notify the app that FIRMessaging server deleted some messages which exceeded storage limits. This
* indicates the "deleted_messages" message type we received from the server.
*/
- (void)didDeleteMessagesOnServer;
@end
/**
* This manages all of the data messages being sent by the client and also the messages that
* were received from the server.
*/
@interface FIRMessagingDataMessageManager : NSObject
NS_ASSUME_NONNULL_BEGIN
- (instancetype)initWithDelegate:(id<FIRMessagingDataMessageManagerDelegate>)delegate
client:(FIRMessagingClient *)client
rmq2Manager:(FIRMessagingRmqManager *)rmq2Manager
syncMessageManager:(FIRMessagingSyncMessageManager *)syncMessageManager;
- (void)setDeviceAuthID:(NSString *)deviceAuthID secretToken:(NSString *)secretToken;
- (void)refreshDelayedMessages;
#pragma mark - Receive
- (NSDictionary *)processPacket:(GtalkDataMessageStanza *)packet;
- (void)didReceiveParsedMessage:(NSDictionary *)message;
#pragma mark - Send
- (void)sendDataMessageStanza:(NSMutableDictionary *)dataMessage;
- (void)didSendDataMessageStanza:(GtalkDataMessageStanza *)message;
- (void)resendMessagesWithConnection:(FIRMessagingConnection *)connection;
NS_ASSUME_NONNULL_END
@end
/*
* Copyright 2017 Google
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef FIRMessaging_xcodeproj_FIRMessagingDefines_h
#define FIRMessaging_xcodeproj_FIRMessagingDefines_h
#define _FIRMessaging_VERBOSE_LOGGING 1
// Verbose Logging
#if (_FIRMessaging_VERBOSE_LOGGING)
#define FIRMessaging_DEV_VERBOSE_LOG(...) NSLog(__VA_ARGS__)
#else
#define FIRMessaging_DEV_VERBOSE_LOG(...) do { } while (0)
#endif // FIRMessaging_VERBOSE_LOGGING
// WEAKIFY & STRONGIFY
// Helper macro.
#define _FIRMessaging_WEAKNAME(VAR) VAR ## _weak_
#define FIRMessaging_WEAKIFY(VAR) __weak __typeof__(VAR) _FIRMessaging_WEAKNAME(VAR) = (VAR);
#define FIRMessaging_STRONGIFY(VAR) \
_Pragma("clang diagnostic push") \
_Pragma("clang diagnostic ignored \"-Wshadow\"") \
__strong __typeof__(VAR) VAR = _FIRMessaging_WEAKNAME(VAR); \
_Pragma("clang diagnostic pop")
// Type Conversions (used for NSInteger etc)
#ifndef _FIRMessaging_L
#define _FIRMessaging_L(v) (long)(v)
#endif
#ifndef _FIRMessaging_UL
#define _FIRMessaging_UL(v) (unsigned long)(v)
#endif
#endif
// Debug Assert
#ifndef _FIRMessagingDevAssert
// we directly invoke the NSAssert handler so we can pass on the varargs
// (NSAssert doesn't have a macro we can use that takes varargs)
#if !defined(NS_BLOCK_ASSERTIONS)
#define _FIRMessagingDevAssert(condition, ...) \
do { \
if (!(condition)) { \
[[NSAssertionHandler currentHandler] \
handleFailureInFunction:(NSString *) \
[NSString stringWithUTF8String:__PRETTY_FUNCTION__] \
file:(NSString *)[NSString stringWithUTF8String:__FILE__] \
lineNumber:__LINE__ \
description:__VA_ARGS__]; \
} \
} while(0)
#else // !defined(NS_BLOCK_ASSERTIONS)
#define _FIRMessagingDevAssert(condition, ...) do { } while (0)
#endif // !defined(NS_BLOCK_ASSERTIONS)
#endif // _FIRMessagingDevAssert
// Invalidates the initializer from which it's called.
#ifndef FIRMessagingInvalidateInitializer
#define FIRMessagingInvalidateInitializer() \
do { \
[self class]; /* Avoid warning of dead store to |self|. */ \
_FIRMessagingDevAssert(NO, @"Invalid initializer."); \
return nil; \
} while (0)
#endif
/*
* Copyright 2017 Google
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#import <Foundation/Foundation.h>
@class GtalkDataMessageStanza;
@class FIRMessagingRmqManager;
@protocol FIRMessagingRmqScanner;
typedef void(^FIRMessagingSendDelayedMessagesHandler)(NSArray *messages);
@interface FIRMessagingDelayedMessageQueue : NSObject
- (instancetype)initWithRmqScanner:(id<FIRMessagingRmqScanner>)rmqScanner
sendDelayedMessagesHandler:(FIRMessagingSendDelayedMessagesHandler)sendDelayedMessagesHandler;
- (BOOL)queueMessage:(GtalkDataMessageStanza *)message;
- (NSArray *)removeDelayedMessages;
@end
/*
* Copyright 2017 Google
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#import "FIRMessagingDelayedMessageQueue.h"
#import "Protos/GtalkCore.pbobjc.h"
#import "FIRMessagingDefines.h"
#import "FIRMessagingRmqManager.h"
#import "FIRMessagingUtilities.h"
static const int kMaxQueuedMessageCount = 10;
@interface FIRMessagingDelayedMessageQueue ()
@property(nonatomic, readonly, weak) id<FIRMessagingRmqScanner> rmqScanner;
@property(nonatomic, readonly, copy) FIRMessagingSendDelayedMessagesHandler sendDelayedMessagesHandler;
@property(nonatomic, readwrite, assign) int persistedMessageCount;
// the scheduled timeout or -1 if not set
@property(nonatomic, readwrite, assign) int64_t scheduledTimeoutMilliseconds;
// The time of the last scan of the message DB,
// used to avoid retrieving messages more than once.
@property(nonatomic, readwrite, assign) int64_t lastDBScanTimestampSeconds;
@property(nonatomic, readwrite, strong) NSMutableArray *messages;
@property(nonatomic, readwrite, strong) NSTimer *sendTimer;
@end
@implementation FIRMessagingDelayedMessageQueue
- (instancetype)init {
FIRMessagingInvalidateInitializer();
}
- (instancetype)initWithRmqScanner:(id<FIRMessagingRmqScanner>)rmqScanner
sendDelayedMessagesHandler:(FIRMessagingSendDelayedMessagesHandler)sendDelayedMessagesHandler {
_FIRMessagingDevAssert(sendDelayedMessagesHandler, @"Invalid nil callback for delayed messages");
self = [super init];
if (self) {
_rmqScanner = rmqScanner;
_sendDelayedMessagesHandler = sendDelayedMessagesHandler;
_messages = [NSMutableArray arrayWithCapacity:10];
_scheduledTimeoutMilliseconds = -1;
}
return self;
}
- (BOOL)queueMessage:(GtalkDataMessageStanza *)message {
if (self.messages.count >= kMaxQueuedMessageCount) {
return NO;
}
if (message.ttl == 0) {
// ttl=0 messages aren't persisted, add it to memory
[self.messages addObject:message];
} else {
self.persistedMessageCount++;
}
int64_t timeoutMillis = [self calculateTimeoutInMillisWithDelayInSeconds:message.maxDelay];
if (![self isTimeoutScheduled] || timeoutMillis < self.scheduledTimeoutMilliseconds) {
[self scheduleTimeoutInMillis:timeoutMillis];
}
return YES;
}
- (NSArray *)removeDelayedMessages {
[self cancelTimeout];
if ([self messageCount] == 0) {
return @[];
}
NSMutableArray *delayedMessages = [NSMutableArray array];
// add the ttl=0 messages
if (self.messages.count) {
[delayedMessages addObjectsFromArray:delayedMessages];
[self.messages removeAllObjects];
}
// add persistent messages
if (self.persistedMessageCount > 0) {
FIRMessaging_WEAKIFY(self);
[self.rmqScanner scanWithRmqMessageHandler:nil
dataMessageHandler:^(int64_t rmqId, GtalkDataMessageStanza *stanza) {
FIRMessaging_STRONGIFY(self);
if ([stanza hasMaxDelay] &&
[stanza sent] >= self.lastDBScanTimestampSeconds) {
[delayedMessages addObject:stanza];
}
}];
self.lastDBScanTimestampSeconds = FIRMessagingCurrentTimestampInSeconds();
self.persistedMessageCount = 0;
}
return delayedMessages;
}
- (void)sendMessages {
if (self.sendDelayedMessagesHandler) {
self.sendDelayedMessagesHandler([self removeDelayedMessages]);
}
}
#pragma mark - Private
- (NSInteger)messageCount {
return self.messages.count + self.persistedMessageCount;
}
- (BOOL)isTimeoutScheduled {
return self.scheduledTimeoutMilliseconds > 0;
}
- (int64_t)calculateTimeoutInMillisWithDelayInSeconds:(int)delay {
return FIRMessagingCurrentTimestampInMilliseconds() + delay * 1000.0;
}
- (void)scheduleTimeoutInMillis:(int64_t)time {
[self cancelTimeout];
self.scheduledTimeoutMilliseconds = time;
double delay = (time - FIRMessagingCurrentTimestampInMilliseconds()) / 1000.0;
[self performSelector:@selector(sendMessages) withObject:self afterDelay:delay];
}
- (void)cancelTimeout {
if ([self isTimeoutScheduled]) {
[NSObject cancelPreviousPerformRequestsWithTarget:self
selector:@selector(sendMessages)
object:nil];
self.scheduledTimeoutMilliseconds = -1;
}
}
@end
/*
* Copyright 2017 Google
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#import "FIRMMessageCode.h"
// The convenience macros are only defined if they haven't already been defined.
#ifndef FIRMessagingLoggerInfo
// Convenience macros that log to the shared FIRMessagingLogger instance. These macros
// are how users should typically log to FIRMessagingLogger.
#define FIRMessagingLoggerDebug(code, ...) \
[FIRMessagingSharedLogger() logFuncDebug:__func__ messageCode:code msg:__VA_ARGS__]
#define FIRMessagingLoggerInfo(code, ...) \
[FIRMessagingSharedLogger() logFuncInfo:__func__ messageCode:code msg:__VA_ARGS__]
#define FIRMessagingLoggerNotice(code, ...) \
[FIRMessagingSharedLogger() logFuncNotice:__func__ messageCode:code msg:__VA_ARGS__]
#define FIRMessagingLoggerWarn(code, ...) \
[FIRMessagingSharedLogger() logFuncWarning:__func__ messageCode:code msg:__VA_ARGS__]
#define FIRMessagingLoggerError(code, ...) \
[FIRMessagingSharedLogger() logFuncError:__func__ messageCode:code msg:__VA_ARGS__]
#endif // !defined(FIRMessagingLoggerInfo)
@interface FIRMessagingLogger : NSObject
- (void)logFuncDebug:(const char *)func
messageCode:(FIRMessagingMessageCode)messageCode
msg:(NSString *)fmt, ... NS_FORMAT_FUNCTION(3, 4);
- (void)logFuncInfo:(const char *)func
messageCode:(FIRMessagingMessageCode)messageCode
msg:(NSString *)fmt, ... NS_FORMAT_FUNCTION(3, 4);
- (void)logFuncNotice:(const char *)func
messageCode:(FIRMessagingMessageCode)messageCode
msg:(NSString *)fmt, ... NS_FORMAT_FUNCTION(3, 4);
- (void)logFuncWarning:(const char *)func
messageCode:(FIRMessagingMessageCode)messageCode
msg:(NSString *)fmt, ... NS_FORMAT_FUNCTION(3, 4);
- (void)logFuncError:(const char *)func
messageCode:(FIRMessagingMessageCode)messageCode
msg:(NSString *)fmt, ... NS_FORMAT_FUNCTION(3, 4);
@end
/**
* Instantiates and/or returns a shared FIRMessagingLogger used exclusively
* for FIRMessaging log messages.
*
* @return the shared FIRMessagingLogger instance
*/
FIRMessagingLogger *FIRMessagingSharedLogger(void);
/*
* Copyright 2017 Google
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#import "FIRMessagingLogger.h"
#import <FirebaseCore/FIRLogger.h>
@implementation FIRMessagingLogger
+ (instancetype)standardLogger {
return [[FIRMessagingLogger alloc] init];
}
#pragma mark - Log Helpers
+ (NSString *)formatMessageCode:(FIRMessagingMessageCode)messageCode {
return [NSString stringWithFormat:@"I-FCM%06ld", (long)messageCode];
}
- (void)logFuncDebug:(const char *)func
messageCode:(FIRMessagingMessageCode)messageCode
msg:(NSString *)fmt, ... {
va_list args;
va_start(args, fmt);
FIRLogBasic(FIRLoggerLevelDebug, kFIRLoggerMessaging,
[FIRMessagingLogger formatMessageCode:messageCode], fmt, args);
va_end(args);
}
- (void)logFuncInfo:(const char *)func
messageCode:(FIRMessagingMessageCode)messageCode
msg:(NSString *)fmt, ... {
va_list args;
va_start(args, fmt);
FIRLogBasic(FIRLoggerLevelInfo, kFIRLoggerMessaging,
[FIRMessagingLogger formatMessageCode:messageCode], fmt, args);
va_end(args);
}
- (void)logFuncNotice:(const char *)func
messageCode:(FIRMessagingMessageCode)messageCode
msg:(NSString *)fmt, ... {
va_list args;
va_start(args, fmt);
FIRLogBasic(FIRLoggerLevelNotice, kFIRLoggerMessaging,
[FIRMessagingLogger formatMessageCode:messageCode], fmt, args);
va_end(args);
}
- (void)logFuncWarning:(const char *)func
messageCode:(FIRMessagingMessageCode)messageCode
msg:(NSString *)fmt, ... {
va_list args;
va_start(args, fmt);
FIRLogBasic(FIRLoggerLevelWarning, kFIRLoggerMessaging,
[FIRMessagingLogger formatMessageCode:messageCode], fmt, args);
va_end(args);
}
- (void)logFuncError:(const char *)func
messageCode:(FIRMessagingMessageCode)messageCode
msg:(NSString *)fmt, ... {
va_list args;
va_start(args, fmt);
FIRLogBasic(FIRLoggerLevelError, kFIRLoggerMessaging,
[FIRMessagingLogger formatMessageCode:messageCode], fmt, args);
va_end(args);
}
@end
FIRMessagingLogger *FIRMessagingSharedLogger(void) {
static dispatch_once_t onceToken;
static FIRMessagingLogger *logger;
dispatch_once(&onceToken, ^{
logger = [FIRMessagingLogger standardLogger];
});
return logger;
}
/*
* Copyright 2017 Google
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#import <Foundation/Foundation.h>
@interface FIRMessagingPacket : NSObject
+ (FIRMessagingPacket *)packetWithTag:(int8_t)tag rmqId:(NSString *)rmqId data:(NSData *)data;
@property(nonatomic, readonly, strong) NSData *data;
@property(nonatomic, readonly, assign) int8_t tag;
// not sent over the wire required for bookkeeping
@property(nonatomic, readonly, assign) NSString *rmqId;
@end
/**
* A queue of the packets(protos) that need to be send over the wire.
*/
@interface FIRMessagingPacketQueue : NSObject
@property(nonatomic, readonly, assign) NSUInteger count;
@property(nonatomic, readonly, assign) BOOL isEmpty;
- (void)push:(FIRMessagingPacket *)packet;
- (void)pushHead:(FIRMessagingPacket *)packet;
- (FIRMessagingPacket *)pop;
@end
/*
* Copyright 2017 Google
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#import "FIRMessagingPacketQueue.h"
#import "FIRMessagingDefines.h"
@interface FIRMessagingPacket ()
@property(nonatomic, readwrite, strong) NSData *data;
@property(nonatomic, readwrite, assign) int8_t tag;
@property(nonatomic, readwrite, assign) NSString *rmqId;
@end
@implementation FIRMessagingPacket
+ (FIRMessagingPacket *)packetWithTag:(int8_t)tag rmqId:(NSString *)rmqId data:(NSData *)data {
return [[self alloc] initWithTag:tag rmqId:rmqId data:data];
}
- (instancetype)init {
FIRMessagingInvalidateInitializer();
}
- (instancetype)initWithTag:(int8_t)tag rmqId:(NSString *)rmqId data:(NSData *)data {
self = [super init];
if (self != nil) {
_data = data;
_tag = tag;
_rmqId = rmqId;
}
return self;
}
- (NSString *)description {
if ([self.rmqId length]) {
return [NSString stringWithFormat:@"<Packet: Tag - %d, Length - %lu>, RmqId - %@",
self.tag, _FIRMessaging_UL(self.data.length), self.rmqId];
} else {
return [NSString stringWithFormat:@"<Packet: Tag - %d, Length - %lu>",
self.tag, _FIRMessaging_UL(self.data.length)];
}
}
@end
@interface FIRMessagingPacketQueue ()
@property(nonatomic, readwrite, strong) NSMutableArray *packetsContainer;
@end
@implementation FIRMessagingPacketQueue;
- (id)init {
self = [super init];
if (self) {
_packetsContainer = [[NSMutableArray alloc] init];
}
return self;
}
- (BOOL)isEmpty {
return self.packetsContainer.count == 0;
}
- (NSUInteger)count {
return self.packetsContainer.count;
}
- (void)push:(FIRMessagingPacket *)packet {
[self.packetsContainer addObject:packet];
}
- (void)pushHead:(FIRMessagingPacket *)packet {
[self.packetsContainer insertObject:packet atIndex:0];
}
- (FIRMessagingPacket *)pop {
if (!self.isEmpty) {
FIRMessagingPacket *packet = self.packetsContainer[0];
[self.packetsContainer removeObjectAtIndex:0];
return packet;
}
return nil;
}
@end
/*
* Copyright 2017 Google
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#import <Foundation/Foundation.h>
#import "FIRMessaging.h"
#import "FIRMessagingTopicsCommon.h"
NS_ASSUME_NONNULL_BEGIN
/**
* Represents a single batch of topics, with the same action.
*
* Topic operations which have the same action (subscribe or unsubscribe) can be executed
* simultaneously, as the order of operations do not matter with the same action. The set of
* topics is unique, as it doesn't make sense to apply the same action to the same topic
* repeatedly; the result would be the same as the first time.
*/
@interface FIRMessagingTopicBatch : NSObject <NSCoding>
@property(nonatomic, readonly, assign) FIRMessagingTopicAction action;
@property(nonatomic, readonly, copy) NSMutableSet <NSString *> *topics;
- (instancetype)init NS_UNAVAILABLE;
- (instancetype)initWithAction:(FIRMessagingTopicAction)action NS_DESIGNATED_INITIALIZER;
@end
@class FIRMessagingPendingTopicsList;
/**
* This delegate must be supplied to the instance of FIRMessagingPendingTopicsList, via the
* @cdelegate property. It lets the
* pending topics list know whether or not it can begin making requests via
* @c-pendingTopicsListCanRequestTopicUpdates:, and handles the request to actually
* perform the topic operation. The delegate also handles when the pending topics list is updated,
* so that it can be archived or persisted.
*
* @see FIRMessagingPendingTopicsList
*/
@protocol FIRMessagingPendingTopicsListDelegate <NSObject>
- (void)pendingTopicsList:(FIRMessagingPendingTopicsList *)list
requestedUpdateForTopic:(NSString *)topic
action:(FIRMessagingTopicAction)action
completion:(FIRMessagingTopicOperationCompletion)completion;
- (void)pendingTopicsListDidUpdate:(FIRMessagingPendingTopicsList *)list;
- (BOOL)pendingTopicsListCanRequestTopicUpdates:(FIRMessagingPendingTopicsList *)list;
@end
/**
* FIRMessagingPendingTopicsList manages a list of topic subscription updates, batched by the same
* action (subscribe or unsubscribe). The list roughly maintains the order of the topic operations,
* batched together whenever the topic action (subscribe or unsubscribe) changes.
*
* Topics operations are batched by action because it is safe to perform the same topic action
* (subscribe or unsubscribe) on many topics simultaneously. After each batch is successfully
* completed, the next batch operations can begin.
*
* When asked to resume its operations, FIRMessagingPendingTopicsList will begin performing updates
* of its current batch of topics. For example, it may begin subscription operations for topics
* [A, B, C] simultaneously.
*
* When the current batch is completed, the next batch of operations will be started. For example
* the list may begin unsubscribe operations for [D, A, E]. Note that because A is in both batches,
* A will be correctly subscribed in the first batch, then unsubscribed as part of the second batch
* of operations. Without batching, it would be ambiguous whether A's subscription operation or the
* unsubscription operation would be completed first.
*
* An app can subscribe and unsubscribe from many topics, and this class helps persist the pending
* topics and perform the operation safely and correctly.
*
* When a topic fails to subscribe or unsubscribe due to a network error, it is considered a
* recoverable error, and so it remains in the current batch until it is succesfully completed.
* Topic updates are completed when they either (a) succeed, (b) are cancelled, or (c) result in an
* unrecoverable error. Any error outside of `NSURLErrorDomain` is considered an unrecoverable
* error.
*
* In addition to maintaining the list of pending topic updates, FIRMessagingPendingTopicsList also
* can track completion handlers for topic operations.
*
* @discussion Completion handlers for topic updates are not maintained if it was restored from a
* keyed archive. They are only called if the topic operation finished within the same app session.
*
* You must supply an object conforming to FIRMessagingPendingTopicsListDelegate in order for the
* topic operations to execute.
*
* @see FIRMessagingPendingTopicsListDelegate
*/
@interface FIRMessagingPendingTopicsList : NSObject <NSCoding>
@property(nonatomic, weak) NSObject <FIRMessagingPendingTopicsListDelegate> *delegate;
@property(nonatomic, readonly, strong, nullable) NSDate *archiveDate;
@property(nonatomic, readonly) NSUInteger numberOfBatches;
- (instancetype)init NS_DESIGNATED_INITIALIZER;
- (void)addOperationForTopic:(NSString *)topic
withAction:(FIRMessagingTopicAction)action
completion:(nullable FIRMessagingTopicOperationCompletion)completion;
- (void)resumeOperationsIfNeeded;
@end
NS_ASSUME_NONNULL_END
/*
* Copyright 2017 Google
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#import "FIRMessagingPendingTopicsList.h"
#import "FIRMessaging_Private.h"
#import "FIRMessagingLogger.h"
#import "FIRMessagingPubSub.h"
#import "FIRMessagingDefines.h"
NSString *const kPendingTopicBatchActionKey = @"action";
NSString *const kPendingTopicBatchTopicsKey = @"topics";
NSString *const kPendingBatchesEncodingKey = @"batches";
NSString *const kPendingTopicsTimestampEncodingKey = @"ts";
#pragma mark - FIRMessagingTopicBatch
@interface FIRMessagingTopicBatch ()
@property(nonatomic, strong, nonnull) NSMutableDictionary
<NSString *, NSMutableArray <FIRMessagingTopicOperationCompletion> *> *topicHandlers;
@end
@implementation FIRMessagingTopicBatch
- (instancetype)initWithAction:(FIRMessagingTopicAction)action {
if (self = [super init]) {
_action = action;
_topics = [NSMutableSet set];
_topicHandlers = [NSMutableDictionary dictionary];
}
return self;
}
#pragma mark NSCoding
- (void)encodeWithCoder:(NSCoder *)aCoder {
[aCoder encodeInteger:self.action forKey:kPendingTopicBatchActionKey];
[aCoder encodeObject:self.topics forKey:kPendingTopicBatchTopicsKey];
}
- (instancetype)initWithCoder:(NSCoder *)aDecoder {
// Ensure that our integer -> enum casting is safe
NSInteger actionRawValue = [aDecoder decodeIntegerForKey:kPendingTopicBatchActionKey];
FIRMessagingTopicAction action = FIRMessagingTopicActionSubscribe;
if (actionRawValue == FIRMessagingTopicActionUnsubscribe) {
action = FIRMessagingTopicActionUnsubscribe;
}
if (self = [self initWithAction:action]) {
NSSet *topics = [aDecoder decodeObjectForKey:kPendingTopicBatchTopicsKey];
if ([topics isKindOfClass:[NSSet class]]) {
_topics = [topics mutableCopy];
}
_topicHandlers = [NSMutableDictionary dictionary];
}
return self;
}
@end
#pragma mark - FIRMessagingPendingTopicsList
@interface FIRMessagingPendingTopicsList ()
@property(nonatomic, readwrite, strong) NSDate *archiveDate;
@property(nonatomic, strong) NSMutableArray <FIRMessagingTopicBatch *> *topicBatches;
@property(nonatomic, strong) FIRMessagingTopicBatch *currentBatch;
@property(nonatomic, strong) NSMutableSet <NSString *> *topicsInFlight;
@end
@implementation FIRMessagingPendingTopicsList
- (instancetype)init {
if (self = [super init]) {
_topicBatches = [NSMutableArray array];
_topicsInFlight = [NSMutableSet set];
}
return self;
}
+ (void)pruneTopicBatches:(NSMutableArray <FIRMessagingTopicBatch *> *)topicBatches {
// For now, just remove empty batches. In the future we can use this to make the subscriptions
// more efficient, by actually pruning topic actions that cancel each other out, for example.
for (NSInteger i = topicBatches.count-1; i >= 0; i--) {
FIRMessagingTopicBatch *batch = topicBatches[i];
if (batch.topics.count == 0) {
[topicBatches removeObjectAtIndex:i];
}
}
}
#pragma mark NSCoding
- (void)encodeWithCoder:(NSCoder *)aCoder {
[aCoder encodeObject:[NSDate date] forKey:kPendingTopicsTimestampEncodingKey];
[aCoder encodeObject:self.topicBatches forKey:kPendingBatchesEncodingKey];
}
- (nullable instancetype)initWithCoder:(NSCoder *)aDecoder {
if (self = [self init]) {
_archiveDate = [aDecoder decodeObjectForKey:kPendingTopicsTimestampEncodingKey];
NSArray *archivedBatches = [aDecoder decodeObjectForKey:kPendingBatchesEncodingKey];
if (archivedBatches) {
_topicBatches = [archivedBatches mutableCopy];
[FIRMessagingPendingTopicsList pruneTopicBatches:_topicBatches];
}
_topicsInFlight = [NSMutableSet set];
}
return self;
}
#pragma mark Getters
- (NSUInteger)numberOfBatches {
return self.topicBatches.count;
}
#pragma mark Adding/Removing topics
- (void)addOperationForTopic:(NSString *)topic
withAction:(FIRMessagingTopicAction)action
completion:(nullable FIRMessagingTopicOperationCompletion)completion {
FIRMessagingTopicBatch *lastBatch = nil;
@synchronized (self) {
lastBatch = self.topicBatches.lastObject;
if (!lastBatch || lastBatch.action != action) {
// There either was no last batch, or our last batch's action was not the same, so we have to
// create a new batch
lastBatch = [[FIRMessagingTopicBatch alloc] initWithAction:action];
[self.topicBatches addObject:lastBatch];
}
BOOL topicExistedBefore = ([lastBatch.topics member:topic] != nil);
if (!topicExistedBefore) {
[lastBatch.topics addObject:topic];
[self.delegate pendingTopicsListDidUpdate:self];
}
// Add the completion handler to the batch
if (completion) {
NSMutableArray *handlers = lastBatch.topicHandlers[topic];
if (!handlers) {
handlers = [[NSMutableArray alloc] init];
}
[handlers addObject:completion];
lastBatch.topicHandlers[topic] = handlers;
}
if (!self.currentBatch) {
self.currentBatch = lastBatch;
}
// This may have been the first topic added, or was added to an ongoing batch
if (self.currentBatch == lastBatch && !topicExistedBefore) {
// Add this topic to our ongoing operations
FIRMessaging_WEAKIFY(self);
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
FIRMessaging_STRONGIFY(self);
[self resumeOperationsIfNeeded];
});
}
}
}
- (void)resumeOperationsIfNeeded {
@synchronized (self) {
// If current batch is not set, set it now
if (!self.currentBatch) {
self.currentBatch = self.topicBatches.firstObject;
}
if (self.currentBatch.topics.count == 0) {
return;
}
if (!self.delegate) {
FIRMessagingLoggerError(kFIRMessagingMessageCodePendingTopicsList000,
@"Attempted to update pending topics without a delegate");
return;
}
if (![self.delegate pendingTopicsListCanRequestTopicUpdates:self]) {
return;
}
for (NSString *topic in self.currentBatch.topics) {
if ([self.topicsInFlight member:topic]) {
// This topic is already active, so skip
continue;
}
[self beginUpdateForCurrentBatchTopic:topic];
}
}
}
- (BOOL)subscriptionErrorIsRecoverable:(NSError *)error {
return [error.domain isEqualToString:NSURLErrorDomain];
}
- (void)beginUpdateForCurrentBatchTopic:(NSString *)topic {
@synchronized (self) {
[self.topicsInFlight addObject:topic];
}
FIRMessaging_WEAKIFY(self);
[self.delegate
pendingTopicsList:self
requestedUpdateForTopic:topic
action:self.currentBatch.action
completion:^(NSError *error) {
dispatch_async(
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
FIRMessaging_STRONGIFY(self);
@synchronized(self) {
[self.topicsInFlight removeObject:topic];
BOOL recoverableError = [self subscriptionErrorIsRecoverable:error];
if (!error || !recoverableError) {
// Notify our handlers and remove the topic from our batch
NSMutableArray *handlers = self.currentBatch.topicHandlers[topic];
if (handlers.count) {
dispatch_async(dispatch_get_main_queue(), ^{
for (FIRMessagingTopicOperationCompletion handler in handlers) {
handler(error);
}
[handlers removeAllObjects];
});
}
[self.currentBatch.topics removeObject:topic];
[self.currentBatch.topicHandlers removeObjectForKey:topic];
if (self.currentBatch.topics.count == 0) {
// All topic updates successfully finished in this batch, move on
// to the next batch
[self.topicBatches removeObject:self.currentBatch];
self.currentBatch = nil;
}
[self.delegate pendingTopicsListDidUpdate:self];
FIRMessaging_WEAKIFY(self);
dispatch_async(
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0),
^{
FIRMessaging_STRONGIFY(self);
[self resumeOperationsIfNeeded];
});
}
}
});
}];
}
@end
/*
* Copyright 2017 Google
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#import <Foundation/Foundation.h>
@interface FIRMessagingPersistentSyncMessage : NSObject
@property(nonatomic, readonly, strong) NSString *rmqID;
@property(nonatomic, readwrite, assign) BOOL apnsReceived;
@property(nonatomic, readwrite, assign) BOOL mcsReceived;
@property(nonatomic, readonly, assign) int64_t expirationTime;
- (instancetype)initWithRMQID:(NSString *)rmqID expirationTime:(int64_t)expirationTime;
@end
/*
* Copyright 2017 Google
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#import "FIRMessagingPersistentSyncMessage.h"
#import "FIRMessagingDefines.h"
@interface FIRMessagingPersistentSyncMessage ()
@property(nonatomic, readwrite, strong) NSString *rmqID;
@property(nonatomic, readwrite, assign) int64_t expirationTime;
@end
@implementation FIRMessagingPersistentSyncMessage
- (instancetype)init {
FIRMessagingInvalidateInitializer();
}
- (instancetype)initWithRMQID:(NSString *)rmqID expirationTime:(int64_t)expirationTime {
self = [super init];
if (self) {
_rmqID = [rmqID copy];
_expirationTime = expirationTime;
}
return self;
}
- (NSString *)description {
NSString *classDescription = NSStringFromClass([self class]);
NSDate *date = [NSDate dateWithTimeIntervalSince1970:self.expirationTime];
return [NSString stringWithFormat:@"%@: (rmqID: %@, apns: %d, mcs: %d, expiry: %@",
classDescription, self.rmqID, self.mcsReceived, self.apnsReceived, date];
}
- (NSString *)debugDescription {
return [self description];
}
@end
/*
* Copyright 2017 Google
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#import "FIRMessaging.h"
NS_ASSUME_NONNULL_BEGIN
@class FIRMessagingClient;
@class FIRMessagingPubSubCache;
/**
* FIRMessagingPubSub provides a publish-subscribe model for sending FIRMessaging topic messages.
*
* An app can subscribe to different topics defined by the
* developer. The app server can then send messages to the subscribed devices
* without having to maintain topic-subscribers mapping. Topics do not
* need to be explicitly created before subscribing or publishing&mdash;they
* are automatically created when publishing or subscribing.
*
* Messages published to the topic will be received as regular FIRMessaging messages
* with `"from"` set to `"/topics/myTopic"`.
*
* Only topic names that match the pattern `"/topics/[a-zA-Z0-9-_.~%]{1,900}"`
* are allowed for subscribing and publishing.
*/
@interface FIRMessagingPubSub : NSObject
@property(nonatomic, readonly, strong) FIRMessagingPubSubCache *cache;
@property(nonatomic, readonly, strong) FIRMessagingClient *client;
/**
* Initializes an instance of FIRMessagingPubSub.
*
* @return An instance of FIRMessagingPubSub.
*/
- (instancetype)initWithClient:(FIRMessagingClient *)client NS_DESIGNATED_INITIALIZER;
/**
* Subscribes an app instance to a topic, enabling it to receive messages
* sent to that topic.
*
* This is an asynchronous call. If subscription fails, FIRMessaging
* invokes the completion callback with the appropriate error.
*
* @see FIRMessagingPubSub unsubscribeWithToken:topic:handler:
*
* @param token The registration token as received from the InstanceID
* library for a given `authorizedEntity` and "gcm" scope.
* @param topic The topic to subscribe to. Should be of the form
* `"/topics/<topic-name>"`.
* @param options Unused parameter, please pass nil or empty dictionary.
* @param handler The callback handler invoked when the subscribe call
* ends. In case of success, a nil error is returned. Otherwise,
* an appropriate error object is returned.
* @discussion This method is thread-safe. However, it is not guaranteed to
* return on the main thread.
*/
- (void)subscribeWithToken:(NSString *)token
topic:(NSString *)topic
options:(nullable NSDictionary *)options
handler:(FIRMessagingTopicOperationCompletion)handler;
/**
* Unsubscribes an app instance from a topic, stopping it from receiving
* any further messages sent to that topic.
*
* This is an asynchronous call. If the attempt to unsubscribe fails,
* we invoke the `completion` callback passed in with an appropriate error.
*
* @param token The token used to subscribe to this topic.
* @param topic The topic to unsubscribe from. Should be of the form
* `"/topics/<topic-name>"`.
* @param options Unused parameter, please pass nil or empty dictionary.
* @param handler The handler that is invoked once the unsubscribe call ends.
* In case of success, nil error is returned. Otherwise, an
* appropriate error object is returned.
* @discussion This method is thread-safe. However, it is not guaranteed to
* return on the main thread.
*/
- (void)unsubscribeWithToken:(NSString *)token
topic:(NSString *)topic
options:(nullable NSDictionary *)options
handler:(FIRMessagingTopicOperationCompletion)handler;
/**
* Asynchronously subscribe to the topic. Adds to the pending list of topic operations.
* Retry in case of failures. This makes a repeated attempt to subscribe to the topic
* as compared to the `subscribe` method above which tries once.
*
* @param topic The topic name to subscribe to. Should be of the form `"/topics/<topic-name>"`.
* @param handler The handler that is invoked once the unsubscribe call ends.
* In case of success, nil error is returned. Otherwise, an
* appropriate error object is returned.
*/
- (void)subscribeToTopic:(NSString *)topic
handler:(nullable FIRMessagingTopicOperationCompletion)handler;
/**
* Asynchronously unsubscribe from the topic. Adds to the pending list of topic operations.
* Retry in case of failures. This makes a repeated attempt to unsubscribe from the topic
* as compared to the `unsubscribe` method above which tries once.
*
* @param topic The topic name to unsubscribe from. Should be of the form `"/topics/<topic-name>"`.
* @param handler The handler that is invoked once the unsubscribe call ends.
* In case of success, nil error is returned. Otherwise, an
* appropriate error object is returned.
*/
- (void)unsubscribeFromTopic:(NSString *)topic
handler:(nullable FIRMessagingTopicOperationCompletion)handler;
/**
* Schedule subscriptions sync.
*
* @param immediately YES if the sync should be scheduled immediately else NO if we can delay
* the sync.
*/
- (void)scheduleSync:(BOOL)immediately;
/**
* Adds the "/topics/" prefix to the topic.
*
* @param topic The topic to add the prefix to.
*
* @return The new topic name with the "/topics/" prefix added.
*/
+ (NSString *)addPrefixToTopic:(NSString *)topic;
/**
* Check if the topic name has "/topics/" prefix.
*
* @param topic The topic name to verify.
*
* @return YES if the topic name has "/topics/" prefix else NO.
*/
+ (BOOL)hasTopicsPrefix:(NSString *)topic;
/**
* Check if it's a valid topic name. This includes "/topics/" prefix in the topic name.
*
* @param topic The topic name to verify.
*
* @return YES if the topic name satisfies the regex "/topics/[a-zA-Z0-9-_.~%]{1,900}".
*/
+ (BOOL)isValidTopicWithPrefix:(NSString *)topic;
@end
NS_ASSUME_NONNULL_END
/*
* Copyright 2017 Google
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#import "FIRMessagingPubSub.h"
#import "FIRMessaging.h"
#import "FIRMessagingClient.h"
#import "FIRMessagingDefines.h"
#import "FIRMessagingLogger.h"
#import "FIRMessagingPendingTopicsList.h"
#import "FIRMessagingUtilities.h"
#import "FIRMessaging_Private.h"
#import "NSDictionary+FIRMessaging.h"
#import "NSError+FIRMessaging.h"
static NSString *const kPendingSubscriptionsListKey =
@"com.firebase.messaging.pending-subscriptions";
@interface FIRMessagingPubSub () <FIRMessagingPendingTopicsListDelegate>
@property(nonatomic, readwrite, strong) FIRMessagingPendingTopicsList *pendingTopicUpdates;
@property(nonatomic, readwrite, strong) FIRMessagingClient *client;
@end
@implementation FIRMessagingPubSub
- (instancetype)init {
FIRMessagingInvalidateInitializer();
// Need this to disable an Xcode warning.
return [self initWithClient:nil];
}
- (instancetype)initWithClient:(FIRMessagingClient *)client {
self = [super init];
if (self) {
_client = client;
[self restorePendingTopicsList];
}
return self;
}
- (void)subscribeWithToken:(NSString *)token
topic:(NSString *)topic
options:(NSDictionary *)options
handler:(FIRMessagingTopicOperationCompletion)handler {
_FIRMessagingDevAssert([token length], @"FIRMessaging error no token specified");
_FIRMessagingDevAssert([topic length], @"FIRMessaging error Invalid empty topic specified");
if (!self.client) {
handler([NSError errorWithFCMErrorCode:kFIRMessagingErrorCodePubSubFIRMessagingNotSetup]);
return;
}
token = [token copy];
topic = [topic copy];
if (![options count]) {
options = @{};
}
if (![[self class] isValidTopicWithPrefix:topic]) {
FIRMessagingLoggerError(kFIRMessagingMessageCodePubSub000,
@"Invalid FIRMessaging Pubsub topic %@", topic);
handler([NSError errorWithFCMErrorCode:kFIRMessagingErrorCodePubSubInvalidTopic]);
return;
}
if (![self verifyPubSubOptions:options]) {
// we do not want to quit even if options have some invalid values.
FIRMessagingLoggerError(kFIRMessagingMessageCodePubSub001,
@"Invalid options passed to FIRMessagingPubSub with non-string keys or "
"values.");
}
// copy the dictionary would trim non-string keys or values if any.
options = [options fcm_trimNonStringValues];
[self.client updateSubscriptionWithToken:token
topic:topic
options:options
shouldDelete:NO
handler:^void(NSError *error) {
handler(error);
}];
}
- (void)unsubscribeWithToken:(NSString *)token
topic:(NSString *)topic
options:(NSDictionary *)options
handler:(FIRMessagingTopicOperationCompletion)handler {
_FIRMessagingDevAssert([token length], @"FIRMessaging error no token specified");
_FIRMessagingDevAssert([topic length], @"FIRMessaging error Invalid empty topic specified");
if (!self.client) {
handler([NSError errorWithFCMErrorCode:kFIRMessagingErrorCodePubSubFIRMessagingNotSetup]);
return;
}
token = [token copy];
topic = [topic copy];
if (![options count]) {
options = @{};
}
if (![[self class] isValidTopicWithPrefix:topic]) {
FIRMessagingLoggerError(kFIRMessagingMessageCodePubSub002,
@"Invalid FIRMessaging Pubsub topic %@", topic);
handler([NSError errorWithFCMErrorCode:kFIRMessagingErrorCodePubSubInvalidTopic]);
return;
}
if (![self verifyPubSubOptions:options]) {
// we do not want to quit even if options have some invalid values.
FIRMessagingLoggerError(
kFIRMessagingMessageCodePubSub003,
@"Invalid options passed to FIRMessagingPubSub with non-string keys or values.");
}
// copy the dictionary would trim non-string keys or values if any.
options = [options fcm_trimNonStringValues];
[self.client updateSubscriptionWithToken:token
topic:topic
options:options
shouldDelete:YES
handler:^void(NSError *error) {
handler(error);
}];
}
- (void)subscribeToTopic:(NSString *)topic
handler:(nullable FIRMessagingTopicOperationCompletion)handler {
[self.pendingTopicUpdates addOperationForTopic:topic
withAction:FIRMessagingTopicActionSubscribe
completion:handler];
}
- (void)unsubscribeFromTopic:(NSString *)topic
handler:(nullable FIRMessagingTopicOperationCompletion)handler {
[self.pendingTopicUpdates addOperationForTopic:topic
withAction:FIRMessagingTopicActionUnsubscribe
completion:handler];
}
- (void)scheduleSync:(BOOL)immediately {
NSString *fcmToken = [[FIRMessaging messaging] defaultFcmToken];
if (fcmToken.length) {
[self.pendingTopicUpdates resumeOperationsIfNeeded];
}
}
#pragma mark - FIRMessagingPendingTopicsListDelegate
- (void)pendingTopicsList:(FIRMessagingPendingTopicsList *)list
requestedUpdateForTopic:(NSString *)topic
action:(FIRMessagingTopicAction)action
completion:(FIRMessagingTopicOperationCompletion)completion {
NSString *fcmToken = [[FIRMessaging messaging] defaultFcmToken];
if (action == FIRMessagingTopicActionSubscribe) {
[self subscribeWithToken:fcmToken topic:topic options:nil handler:completion];
} else {
[self unsubscribeWithToken:fcmToken topic:topic options:nil handler:completion];
}
}
- (void)pendingTopicsListDidUpdate:(FIRMessagingPendingTopicsList *)list {
[self archivePendingTopicsList:list];
}
- (BOOL)pendingTopicsListCanRequestTopicUpdates:(FIRMessagingPendingTopicsList *)list {
NSString *fcmToken = [[FIRMessaging messaging] defaultFcmToken];
return (fcmToken.length > 0);
}
#pragma mark - Storing Pending Topics
- (void)archivePendingTopicsList:(FIRMessagingPendingTopicsList *)topicsList {
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSData *pendingData = [NSKeyedArchiver archivedDataWithRootObject:topicsList];
[defaults setObject:pendingData forKey:kPendingSubscriptionsListKey];
[defaults synchronize];
}
- (void)restorePendingTopicsList {
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSData *pendingData = [defaults objectForKey:kPendingSubscriptionsListKey];
FIRMessagingPendingTopicsList *subscriptions;
@try {
if (pendingData) {
subscriptions = [NSKeyedUnarchiver unarchiveObjectWithData:pendingData];
}
} @catch (NSException *exception) {
// Nothing we can do, just continue as if we don't have pending subscriptions
} @finally {
if (subscriptions) {
self.pendingTopicUpdates = subscriptions;
} else {
self.pendingTopicUpdates = [[FIRMessagingPendingTopicsList alloc] init];
}
self.pendingTopicUpdates.delegate = self;
}
}
#pragma mark - Private Helpers
- (BOOL)verifyPubSubOptions:(NSDictionary *)options {
return ![options fcm_hasNonStringKeysOrValues];
}
#pragma mark - Topic Name Helpers
static NSString *const kTopicsPrefix = @"/topics/";
static NSString *const kTopicRegexPattern = @"/topics/([a-zA-Z0-9-_.~%]+)";
+ (NSString *)addPrefixToTopic:(NSString *)topic {
if (![self hasTopicsPrefix:topic]) {
return [NSString stringWithFormat:@"%@%@", kTopicsPrefix, topic];
} else {
return [topic copy];
}
}
+ (BOOL)hasTopicsPrefix:(NSString *)topic {
return [topic hasPrefix:kTopicsPrefix];
}
/**
* Returns a regular expression for matching a topic sender.
*
* @return The topic matching regular expression
*/
+ (NSRegularExpression *)topicRegex {
// Since this is a static regex pattern, we only only need to declare it once.
static NSRegularExpression *topicRegex;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSError *error;
topicRegex =
[NSRegularExpression regularExpressionWithPattern:kTopicRegexPattern
options:NSRegularExpressionAnchorsMatchLines
error:&error];
});
return topicRegex;
}
/**
* Gets the class describing occurences of topic names and sender IDs in the sender.
*
* @param expression The topic expression used to generate a pubsub topic
*
* @return Representation of captured subexpressions in topic regular expression
*/
+ (BOOL)isValidTopicWithPrefix:(NSString *)topic {
NSRange topicRange = NSMakeRange(0, topic.length);
NSRange regexMatchRange = [[self topicRegex] rangeOfFirstMatchInString:topic
options:NSMatchingAnchored
range:topicRange];
return NSEqualRanges(topicRange, regexMatchRange);
}
@end
/*
* Copyright 2017 Google
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#import "FIRMessagingTopicOperation.h"
@class FIRMessagingCheckinService;
@interface FIRMessagingPubSubRegistrar : NSObject
/**
* Designated Initializer.
*
* @param checkinService The checkin service used to register with Checkin
* server.
*
* @return A new FIRMessagingPubSubRegistrar instance used to subscribe/unsubscribe.
*/
- (instancetype)initWithCheckinService:(FIRMessagingCheckinService *)checkinService;
/**
* Stops all the subscription requests going on in parallel. This would
* invalidate all the handlers associated with the subscription requests.
*/
- (void)stopAllSubscriptionRequests;
/**
* Update subscription status for a given topic with FIRMessaging's backend.
*
* @param topic The topic to subscribe to.
* @param token The registration token to be used.
* @param options The options to be passed in during subscription request.
* @param shouldDelete NO if the subscription is being added else YES if being
* removed.
* @param handler The handler invoked once the update subscription request
* finishes.
*/
- (void)updateSubscriptionToTopic:(NSString *)topic
withToken:(NSString *)token
options:(NSDictionary *)options
shouldDelete:(BOOL)shouldDelete
handler:(FIRMessagingTopicOperationCompletion)handler;
@end
/*
* Copyright 2017 Google
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#import "FIRMessagingPubSubRegistrar.h"
#import "FIRMessagingCheckinService.h"
#import "FIRMessagingDefines.h"
#import "FIRMessagingPubSubRegistrar.h"
#import "FIRMessagingTopicsCommon.h"
#import "NSError+FIRMessaging.h"
@interface FIRMessagingPubSubRegistrar ()
@property(nonatomic, readwrite, strong) FIRMessagingCheckinService *checkinService;
@property(nonatomic, readonly, strong) NSOperationQueue *topicOperations;
// Common errors, instantiated, to avoid generating multiple copies
@property(nonatomic, readwrite, strong) NSError *operationInProgressError;
@end
@implementation FIRMessagingPubSubRegistrar
- (instancetype)init {
FIRMessagingInvalidateInitializer();
}
- (instancetype)initWithCheckinService:(FIRMessagingCheckinService *)checkinService {
self = [super init];
if (self) {
_checkinService = checkinService;
_topicOperations = [[NSOperationQueue alloc] init];
// Do 10 topic operations at a time; it's enough to keep the TCP connection to the host alive,
// saving hundreds of milliseconds on each request (compared to a serial queue).
_topicOperations.maxConcurrentOperationCount = 10;
}
return self;
}
- (void)stopAllSubscriptionRequests {
[self.topicOperations cancelAllOperations];
}
- (void)updateSubscriptionToTopic:(NSString *)topic
withToken:(NSString *)token
options:(NSDictionary *)options
shouldDelete:(BOOL)shouldDelete
handler:(FIRMessagingTopicOperationCompletion)handler {
FIRMessagingTopicAction action = FIRMessagingTopicActionSubscribe;
if (shouldDelete) {
action = FIRMessagingTopicActionUnsubscribe;
}
FIRMessagingTopicOperation *operation =
[[FIRMessagingTopicOperation alloc] initWithTopic:topic
action:action
token:token
options:options
checkinService:self.checkinService
completion:handler];
[self.topicOperations addOperation:operation];
}
@end
/*
* Copyright 2017 Google
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#import "FIRMessagingDataMessageManager.h"
#import "FIRMessaging.h"
@class FIRMessagingReceiver;
@protocol FIRMessagingReceiverDelegate <NSObject>
- (void)receiver:(nonnull FIRMessagingReceiver *)receiver
receivedRemoteMessage:(nonnull FIRMessagingRemoteMessage *)remoteMessage;
@end
@interface FIRMessagingReceiver : NSObject <FIRMessagingDataMessageManagerDelegate>
@property(nonatomic, weak, nullable) id<FIRMessagingReceiverDelegate> delegate;
@end
/*
* Copyright 2017 Google
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#import "FIRMessagingReceiver.h"
#import <UIKit/UIKit.h>
#import "FIRMessaging.h"
#import "FIRMessaging_Private.h"
#import "FIRMessagingLogger.h"
static NSString *const kUpstreamMessageIDUserInfoKey = @"messageID";
static NSString *const kUpstreamErrorUserInfoKey = @"error";
// Copied from Apple's header in case it is missing in some cases.
#ifndef NSFoundationVersionNumber_iOS_9_x_Max
#define NSFoundationVersionNumber_iOS_9_x_Max 1299
#endif
static int downstreamMessageID = 0;
@implementation FIRMessagingReceiver
#pragma mark - FIRMessagingDataMessageManager protocol
- (void)didReceiveMessage:(NSDictionary *)message withIdentifier:(nullable NSString *)messageID {
if (![messageID length]) {
messageID = [[self class] nextMessageID];
}
if (floor(NSFoundationVersionNumber) > NSFoundationVersionNumber_iOS_9_x_Max) {
// Use delegate method for iOS 10
[self scheduleIos10NotificationForMessage:message withIdentifier:messageID];
} else {
// Post notification directly to AppDelegate handlers. This is valid pre-iOS 10.
[self scheduleNotificationForMessage:message];
}
}
- (void)willSendDataMessageWithID:(NSString *)messageID error:(NSError *)error {
NSNotification *notification;
if (error) {
NSDictionary *userInfo = @{
kUpstreamMessageIDUserInfoKey : [messageID copy],
kUpstreamErrorUserInfoKey : error
};
notification = [NSNotification notificationWithName:FIRMessagingSendErrorNotification
object:nil
userInfo:userInfo];
[[NSNotificationQueue defaultQueue] enqueueNotification:notification postingStyle:NSPostASAP];
FIRMessagingLoggerDebug(kFIRMessagingMessageCodeReceiver000,
@"Fail to send upstream message: %@ error: %@", messageID, error);
} else {
FIRMessagingLoggerDebug(kFIRMessagingMessageCodeReceiver001, @"Will send upstream message: %@",
messageID);
}
}
- (void)didSendDataMessageWithID:(NSString *)messageID {
// invoke the callbacks asynchronously
FIRMessagingLoggerDebug(kFIRMessagingMessageCodeReceiver002, @"Did send upstream message: %@",
messageID);
NSNotification * notification =
[NSNotification notificationWithName:FIRMessagingSendSuccessNotification
object:nil
userInfo:@{ kUpstreamMessageIDUserInfoKey : [messageID copy] }];
[[NSNotificationQueue defaultQueue] enqueueNotification:notification postingStyle:NSPostASAP];
}
- (void)didDeleteMessagesOnServer {
FIRMessagingLoggerDebug(kFIRMessagingMessageCodeReceiver003,
@"Will send deleted messages notification");
NSNotification * notification =
[NSNotification notificationWithName:FIRMessagingMessagesDeletedNotification
object:nil];
[[NSNotificationQueue defaultQueue] enqueueNotification:notification postingStyle:NSPostASAP];
}
#pragma mark - Private Helpers
// As the new UserNotifications framework in iOS 10 doesn't support constructor/mutation for
// UNNotification object, FCM can't inject the message to the app with UserNotifications framework.
// Define our own protocol, which means app developers need to implement two interfaces to receive
// display notifications and data messages respectively for devices running iOS 10 or above. Devices
// running iOS 9 or below are not affected.
- (void)scheduleIos10NotificationForMessage:(NSDictionary *)message
withIdentifier:(NSString *)messageID {
FIRMessagingRemoteMessage *wrappedMessage = [[FIRMessagingRemoteMessage alloc] init];
// TODO: wrap title, body, badge and other fields
wrappedMessage.appData = [message copy];
[self.delegate receiver:self receivedRemoteMessage:wrappedMessage];
}
- (void)scheduleNotificationForMessage:(NSDictionary *)message {
SEL newNotificationSelector =
@selector(application:didReceiveRemoteNotification:fetchCompletionHandler:);
SEL oldNotificationSelector = @selector(application:didReceiveRemoteNotification:);
dispatch_async(dispatch_get_main_queue(), ^{
id<UIApplicationDelegate> appDelegate = [[UIApplication sharedApplication] delegate];
if ([appDelegate respondsToSelector:newNotificationSelector]) {
// Try the new remote notification callback
[appDelegate application:[UIApplication sharedApplication]
didReceiveRemoteNotification:message
fetchCompletionHandler:^(UIBackgroundFetchResult result) {}];
} else if ([appDelegate respondsToSelector:oldNotificationSelector]) {
// Try the old remote notification callback
[appDelegate application:
[UIApplication sharedApplication] didReceiveRemoteNotification:message];
} else {
FIRMessagingLoggerError(kFIRMessagingMessageCodeReceiver005,
@"None of the remote notification callbacks implemented by "
@"UIApplicationDelegate");
}
});
}
+ (NSString *)nextMessageID {
@synchronized (self) {
++downstreamMessageID;
return [NSString stringWithFormat:@"gcm-%d", downstreamMessageID];
}
}
@end
/*
* Copyright 2017 Google
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#import "FIRMessaging.h"
#import "FIRMessagingCheckinService.h"
@class FIRMessagingCheckinStore;
@class FIRMessagingPubSubRegistrar;
/**
* Handle the registration process for the client. Fetch checkin information from the Checkin
* service if not cached on the device and then try to register the client with FIRMessaging backend.
*/
@interface FIRMessagingRegistrar : NSObject
@property(nonatomic, readonly, strong) FIRMessagingPubSubRegistrar *pubsubRegistrar;
@property(nonatomic, readonly, strong) NSString *deviceAuthID;
@property(nonatomic, readonly, strong) NSString *secretToken;
/**
* Initialize a FIRMessaging Registrar.
*
* @return A FIRMessaging Registrar object.
*/
- (instancetype)init NS_DESIGNATED_INITIALIZER;
#pragma mark - Checkin
/**
* Try to load checkin info from the disk if not currently loaded into memory.
*
* @return YES if successfully loaded valid checkin info to memory else NO.
*/
- (BOOL)tryToLoadValidCheckinInfo;
/**
* Check if we have a valid checkin info in memory.
*
* @return YES if we have valid checkin info in memory else NO.
*/
- (BOOL)hasValidCheckinInfo;
#pragma mark - Subscribe/Unsubscribe
/**
* Update the subscription for a given topic for the client.
*
* @param topic The topic for which the subscription should be updated.
* @param token The registration token to be used by the client.
* @param options The extra options if any being passed as part of
* subscription request.
* @param shouldDelete YES if we want to delete an existing subscription else NO
* if we want to create a new subscription.
* @param handler The handler to invoke once the subscription request is
* complete.
*/
- (void)updateSubscriptionToTopic:(NSString *)topic
withToken:(NSString *)token
options:(NSDictionary *)options
shouldDelete:(BOOL)shouldDelete
handler:(FIRMessagingTopicOperationCompletion)handler;
/**
* Cancel all subscription requests as well as any requests to checkin. Note if
* there are subscription requests waiting on checkin to complete those requests
* would be marked as stale and be NO-OP's if they happen in the future.
*
* Also note this is a one time operation, you should only call this if you want
* to immediately stop all requests and deallocate the registrar. After calling
* this once you would no longer be able to use this registrar object.
*/
- (void)cancelAllRequests;
@end
/*
* Copyright 2017 Google
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#import "FIRMessagingRegistrar.h"
#import "FIRMessagingDefines.h"
#import "FIRMessagingLogger.h"
#import "FIRMessagingPubSubRegistrar.h"
#import "FIRMessagingUtilities.h"
#import "NSError+FIRMessaging.h"
@interface FIRMessagingRegistrar ()
@property(nonatomic, readwrite, assign) BOOL stopAllSubscriptions;
@property(nonatomic, readwrite, strong) FIRMessagingCheckinService *checkinService;
@property(nonatomic, readwrite, strong) FIRMessagingPubSubRegistrar *pubsubRegistrar;
@end
@implementation FIRMessagingRegistrar
- (NSString *)deviceAuthID {
return self.checkinService.deviceAuthID;
}
- (NSString *)secretToken {
return self.checkinService.secretToken;
}
- (instancetype)init {
self = [super init];
if (self) {
_checkinService = [[FIRMessagingCheckinService alloc] init];
// TODO(chliangGoogle): Merge pubsubRegistrar with Registrar as it is hard to track how many
// checkinService instances by separating classes too often.
_pubsubRegistrar = [[FIRMessagingPubSubRegistrar alloc] initWithCheckinService:_checkinService];
}
return self;
}
#pragma mark - Checkin
- (BOOL)tryToLoadValidCheckinInfo {
[self.checkinService tryToLoadPrefetchedCheckinPreferences];
return [self.checkinService hasValidCheckinInfo];
}
- (BOOL)hasValidCheckinInfo {
return [self.checkinService hasValidCheckinInfo];
}
#pragma mark - Subscribe/Unsubscribe
- (void)updateSubscriptionToTopic:(NSString *)topic
withToken:(NSString *)token
options:(NSDictionary *)options
shouldDelete:(BOOL)shouldDelete
handler:(FIRMessagingTopicOperationCompletion)handler {
_FIRMessagingDevAssert(handler, @"Invalid nil handler");
if ([self tryToLoadValidCheckinInfo]) {
[self doUpdateSubscriptionForTopic:topic
token:token
options:options
shouldDelete:shouldDelete
completion:handler];
} else {
FIRMessagingLoggerDebug(kFIRMessagingMessageCodeRegistrar000,
@"Device check in error, no auth credentials found");
NSError *error = [NSError errorWithFCMErrorCode:kFIRMessagingErrorCodeMissingDeviceID];
handler(error);
}
}
- (void)cancelAllRequests {
self.stopAllSubscriptions = YES;
[self.pubsubRegistrar stopAllSubscriptionRequests];
}
#pragma mark - Private
- (void)doUpdateSubscriptionForTopic:(NSString *)topic
token:(NSString *)token
options:(NSDictionary *)options
shouldDelete:(BOOL)shouldDelete
completion:(FIRMessagingTopicOperationCompletion)completion {
_FIRMessagingDevAssert([self.checkinService hasValidCheckinInfo],
@"No valid checkin info found before subscribe");
[self.pubsubRegistrar updateSubscriptionToTopic:topic
withToken:token
options:options
shouldDelete:shouldDelete
handler:completion];
}
@end
/*
* Copyright 2017 Google
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#import <Foundation/Foundation.h>
/**
* Swizzle remote-notification callbacks to invoke FIRMessaging methods
* before calling original implementations.
*/
@interface FIRMessagingRemoteNotificationsProxy : NSObject
/**
* Checks the `FirebaseAppDelegateProxyEnabled` key in the App's Info.plist. If the key is
* missing or incorrectly formatted, returns `YES`.
*
* @return YES if the Application Delegate and User Notification Center methods can be swizzled.
* Otherwise, returns NO.
*/
+ (BOOL)canSwizzleMethods;
/**
* Swizzles Application Delegate's remote-notification callbacks and User Notification Center
* delegate callback, and invokes the original selectors once done.
*/
+ (void)swizzleMethods;
@end
/*
* Copyright 2017 Google
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#import <Foundation/Foundation.h>
@class FIRMessagingPersistentSyncMessage;
// table data handlers
/**
* Handle message stored in the outgoing RMQ messages table.
*
* @param rmqId The rmqID of the message.
* @param tag The message tag.
* @param data The data stored in the message.
*/
typedef void(^FCMOutgoingRmqMessagesTableHandler)(int64_t rmqId, int8_t tag, NSData *data);
/// Outgoing messages RMQ table
extern NSString *const kTableOutgoingRmqMessages;
/// Server to device RMQ table
extern NSString *const kTableS2DRmqIds;
@interface FIRMessagingRmq2PersistentStore : NSObject
/**
* Initialize and open the RMQ database on the client.
*
* @param databaseName The name for RMQ database.
*
* @return A store used to persist messages on the client.
*/
- (instancetype)initWithDatabaseName:(NSString *)databaseName;
/**
* Save outgoing message in RMQ.
*
* @param rmqId The rmqID for the message.
* @param tag The tag of the message proto.
* @param data The data being sent in the message.
* @param error The error if any while saving the message to the persistent store.
*
* @return YES if the message was successfully saved to the persistent store else NO.
*/
- (BOOL)saveMessageWithRmqId:(int64_t)rmqId
tag:(int8_t)tag
data:(NSData *)data
error:(NSError **)error;
/**
* Add unacked server to device message with a given rmqID to the persistent store.
*
* @param rmqId The rmqID of the message that was not acked by the cient.
*
* @return YES if the save was successful else NO.
*/
- (BOOL)saveUnackedS2dMessageWithRmqId:(NSString *)rmqId;
/**
* Update the last RMQ ID that was sent by the client.
*
* @param rmqID The latest rmqID sent by the device.
*
* @return YES if the last rmqID was successfully saved else NO.
*/
- (BOOL)updateLastOutgoingRmqId:(int64_t)rmqID;
#pragma mark - Query
/**
* Query the highest rmqID saved in the Outgoing messages table.
*
* @return The highest rmqID amongst all the messages in the Outgoing RMQ table. If no message
* was ever persisted return 0.
*/
- (int64_t)queryHighestRmqId;
/**
* The last rmqID that was saved on the client.
*
* @return The last rmqID that was saved. If no rmqID was ever persisted return 0.
*/
- (int64_t)queryLastRmqId;
/**
* Get a list of all unacked server to device messages stored on the client.
*
* @return List of all unacked s2d messages in the persistent store.
*/
- (NSArray *)unackedS2dRmqIds;
/**
* Iterate over all outgoing messages in the RMQ table.
*
* @param handler The handler invoked with each message in the outgoing RMQ table.
*/
- (void)scanOutgoingRmqMessagesWithHandler:(FCMOutgoingRmqMessagesTableHandler)handler;
#pragma mark - Delete
/**
* Delete messages with given rmqID's from a table.
*
* @param tableName The table name from which to delete the rmq messages.
* @param rmqIds The rmqID's of the messages to be deleted.
*
* @return The number of messages that were successfully deleted.
*/
- (int)deleteMessagesFromTable:(NSString *)tableName
withRmqIds:(NSArray *)rmqIds;
/**
* Remove database from the device.
*
* @param dbName The database name to be deleted.
*/
+ (void)removeDatabase:(NSString *)dbName;
#pragma mark - Sync Messages
/**
* Save sync message to persistent store to check for duplicates.
*
* @param rmqID The rmqID of the message to save.
* @param expirationTime The expiration time of the message to save.
* @param apnsReceived YES if the message was received via APNS else NO.
* @param mcsReceived YES if the message was received via MCS else NO.
* @param error The error if any while saving the message to store.
*
* @return YES if the message was saved successfully else NO.
*/
- (BOOL)saveSyncMessageWithRmqID:(NSString *)rmqID
expirationTime:(int64_t)expirationTime
apnsReceived:(BOOL)apnsReceived
mcsReceived:(BOOL)mcsReceived
error:(NSError **)error;
/**
* Update sync message received via APNS.
*
* @param rmqID The rmqID of the sync message.
* @param error The error if any while updating the sync message in persistence.
*
* @return YES if the update was successful else NO.
*/
- (BOOL)updateSyncMessageViaAPNSWithRmqID:(NSString *)rmqID
error:(NSError **)error;
/**
* Update sync message received via MCS.
*
* @param rmqID The rmqID of the sync message.
* @param error The error if any while updating the sync message in persistence.
*
* @return YES if the update was successful else NO.
*/
- (BOOL)updateSyncMessageViaMCSWithRmqID:(NSString *)rmqID
error:(NSError **)error;
/**
* Query sync message table for a given rmqID.
*
* @param rmqID The rmqID to search for in SYNC_RMQ.
*
* @return The sync message that was persisted with `rmqID`. If no such message was persisted
* return nil.
*/
- (FIRMessagingPersistentSyncMessage *)querySyncMessageWithRmqID:(NSString *)rmqID;
/**
* Delete sync message with rmqID.
*
* @param rmqID The rmqID of the message to delete.
*
* @return YES if a sync message with rmqID was found and deleted successfully else NO.
*/
- (BOOL)deleteSyncMessageWithRmqID:(NSString *)rmqID;
/**
* Delete the expired sync messages from persisten store. Also deletes messages that have been
* delivered both via APNS and MCS.
*
* @param error The error if any while deleting the messages.
*
* @return The total number of messages that were deleted from the persistent store.
*/
- (int)deleteExpiredOrFinishedSyncMessages:(NSError **)error;
@end
/*
* Copyright 2017 Google
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#import <Foundation/Foundation.h>
@class GtalkDataMessageStanza;
@class GPBMessage;
@class FIRMessagingPersistentSyncMessage;
/**
* Called on each raw message.
*/
typedef void(^FIRMessagingRmqMessageHandler)(int64_t rmqId, int8_t tag, NSData *data);
/**
* Called on each DataMessageStanza.
*/
typedef void(^FIRMessagingDataMessageHandler)(int64_t rmqId, GtalkDataMessageStanza *stanza);
/**
* Used to scan through the rmq and perform actions on messages as required.
*/
@protocol FIRMessagingRmqScanner <NSObject>
/**
* Scan the RMQ for outgoing messages and process them as required.
*/
- (void)scanWithRmqMessageHandler:(FIRMessagingRmqMessageHandler)rmqMessageHandler
dataMessageHandler:(FIRMessagingDataMessageHandler)dataMessageHandler;
@end
/**
* This manages the RMQ persistent store.
*
* The store is used to store all the S2D id's that were received by the client and were ACK'ed
* by us but the server hasn't confirmed the ACK. We don't delete these id's until the server
* ACK's us that they have received them.
*
* We also store the upstream messages(d2s) that were sent by the client.
*
* Also store the lastRMQId that was sent by us so that for a new connection being setup we don't
* duplicate RMQ Id's for the new messages.
*/
@interface FIRMessagingRmqManager : NSObject <FIRMessagingRmqScanner>
// designated initializer
- (instancetype)initWithDatabaseName:(NSString *)databaseName;
- (void)loadRmqId;
/**
* Save an upstream message to RMQ. If the message send fails for some reason we would not
* lose the message since it would be saved in the RMQ.
*
* @param message The upstream message to be saved.
* @param error The error if any while saving the message else nil.
*
* @return YES if the message was successfully saved to RMQ else NO.
*/
- (BOOL)saveRmqMessage:(GPBMessage *)message error:(NSError **)error;
/**
* Save Server to device message with the given RMQ-ID.
*
* @param rmqID The rmqID of the s2d message to save.
*
* @return YES if the save was successfull else NO.
*/
- (BOOL)saveS2dMessageWithRmqId:(NSString *)rmqID;
/**
* A list of all unacked Server to device RMQ IDs.
*
* @return A list of unacked Server to Device RMQ ID's. All values are Strings.
*/
- (NSArray *)unackedS2dRmqIds;
/**
* Removes the outgoing message from RMQ store.
*
* @param rmqId The rmqID to remove from the store.
*
* @return The number of messages deleted successfully.
*/
- (int)removeRmqMessagesWithRmqId:(NSString *)rmqId;
/**
* Removes the messages with the given rmqIDs from RMQ store.
*
* @param rmqIds The lsit of rmqID's to remove from the store.
*
* @return The number of messages deleted successfully.
*/
- (int)removeRmqMessagesWithRmqIds:(NSArray *)rmqIds;
/**
* Removes a list of downstream messages from the RMQ.
*
* @param s2dIds The list of messages ACK'ed by the server that we should remove
* from the RMQ store.
*/
- (void)removeS2dIds:(NSArray *)s2dIds;
#pragma mark - Sync Messages
/**
* Get persisted sync message with rmqID.
*
* @param rmqID The rmqID of the persisted sync message.
*
* @return A valid persistent sync message with the given rmqID if found in the RMQ else nil.
*/
- (FIRMessagingPersistentSyncMessage *)querySyncMessageWithRmqID:(NSString *)rmqID;
/**
* Delete sync message with rmqID.
*
* @param rmqID The rmqID of the persisted sync message.
*
* @return YES if the message was successfully deleted else NO.
*/
- (BOOL)deleteSyncMessageWithRmqID:(NSString *)rmqID;
/**
* Delete the expired sync messages from persisten store. Also deletes messages that have been
* delivered both via APNS and MCS.
*
* @param error The error if any while deleting the messages.
*
* @return The total number of messages that were deleted from the persistent store.
*/
- (int)deleteExpiredOrFinishedSyncMessages:(NSError **)error;
/**
* Save sync message received by the device.
*
* @param rmqID The rmqID of the message received.
* @param expirationTime The expiration time of the sync message received.
* @param apnsReceived YES if the message was received via APNS else NO.
* @param mcsReceived YES if the message was received via MCS else NO.
* @param error The error if any while saving the sync message to persistent store.
*
* @return YES if the message save was successful else NO.
*/
- (BOOL)saveSyncMessageWithRmqID:(NSString *)rmqID
expirationTime:(int64_t)expirationTime
apnsReceived:(BOOL)apnsReceived
mcsReceived:(BOOL)mcsReceived
error:(NSError **)error;
/**
* Update sync message received via APNS.
*
* @param rmqID The rmqID of the received message.
* @param error The error if any while updating the sync message.
*
* @return YES if the persistent sync message was successfully updated else NO.
*/
- (BOOL)updateSyncMessageViaAPNSWithRmqID:(NSString *)rmqID error:(NSError **)error;
/**
* Update sync message received via MCS.
*
* @param rmqID The rmqID of the received message.
* @param error The error if any while updating the sync message.
*
* @return YES if the persistent sync message was successfully updated else NO.
*/
- (BOOL)updateSyncMessageViaMCSWithRmqID:(NSString *)rmqID error:(NSError **)error;
#pragma mark - Testing
+ (void)removeDatabaseWithName:(NSString *)dbName;
@end
/*
* Copyright 2017 Google
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#import "FIRMessagingRmqManager.h"
#import "Protos/GtalkCore.pbobjc.h"
#import "sqlite3.h"
#import "FIRMessagingDefines.h"
#import "FIRMessagingLogger.h"
#import "FIRMessagingRmq2PersistentStore.h"
#import "FIRMessagingUtilities.h"
#ifndef _FIRMessagingRmqLogAndExit
#define _FIRMessagingRmqLogAndExit(stmt, return_value) \
do { \
[self logErrorAndFinalizeStatement:stmt]; \
return return_value; \
} while(0)
#endif
static NSString *const kFCMRmqTag = @"FIRMessagingRmq:";
@interface FIRMessagingRmqManager ()
@property(nonatomic, readwrite, strong) FIRMessagingRmq2PersistentStore *rmq2Store;
// map the category of an outgoing message with the number of messages for that category
// should always have two keys -- the app, gcm
@property(nonatomic, readwrite, strong) NSMutableDictionary *outstandingMessages;
// Outgoing RMQ persistent id
@property(nonatomic, readwrite, assign) int64_t rmqId;
@end
@implementation FIRMessagingRmqManager
- (instancetype)initWithDatabaseName:(NSString *)databaseName {
self = [super init];
if (self) {
_FIRMessagingDevAssert([databaseName length] > 0, @"RMQ: Invalid rmq db name");
_rmq2Store = [[FIRMessagingRmq2PersistentStore alloc] initWithDatabaseName:databaseName];
_outstandingMessages = [NSMutableDictionary dictionaryWithCapacity:2];
_rmqId = -1;
}
return self;
}
- (void)loadRmqId {
if (self.rmqId >= 0) {
return; // already done
}
[self loadInitialOutgoingPersistentId];
if (self.outstandingMessages.count) {
FIRMessagingLoggerDebug(kFIRMessagingMessageCodeRmqManager000,
@"%@: outstanding categories %ld", kFCMRmqTag,
_FIRMessaging_UL(self.outstandingMessages.count));
}
}
/**
* Initialize the 'initial RMQ':
* - max ID of any message in the queue
* - if the queue is empty, stored value in separate DB.
*
* Stream acks will remove from RMQ, when we remove the highest message we keep track
* of its ID.
*/
- (void)loadInitialOutgoingPersistentId {
// we shouldn't always trust the lastRmqId stored in the LastRmqId table, because
// we only save to the LastRmqId table once in a while (after getting the lastRmqId sent
// by the server after reconnect, and after getting a rmq ack from the server). The
// rmq message with the highest rmq id tells the real story, so check against that first.
int64_t rmqId = [self queryHighestRmqId];
if (rmqId == 0) {
rmqId = [self querylastRmqId];
}
self.rmqId = rmqId + 1;
}
#pragma mark - Save
/**
* Save a message to RMQ2. Will populate the rmq2 persistent ID.
*/
- (BOOL)saveRmqMessage:(GPBMessage *)message
error:(NSError **)error {
// send using rmq2manager
// the wire format of rmq2 id is a string. However, we keep it as a long internally
// in the database. So only convert the id to string when preparing for sending over
// the wire.
NSString *rmq2Id = FIRMessagingGetRmq2Id(message);
if (![rmq2Id length]) {
int64_t rmqId = [self nextRmqId];
rmq2Id = [NSString stringWithFormat:@"%lld", rmqId];
FIRMessagingSetRmq2Id(message, rmq2Id);
}
FIRMessagingProtoTag tag = FIRMessagingGetTagForProto(message);
return [self saveMessage:message withRmqId:[rmq2Id integerValue] tag:tag error:error];
}
- (BOOL)saveMessage:(GPBMessage *)message
withRmqId:(int64_t)rmqId
tag:(int8_t)tag
error:(NSError **)error {
NSData *data = [message data];
return [self.rmq2Store saveMessageWithRmqId:rmqId tag:tag data:data error:error];
}
/**
* This is called when we delete the largest outgoing message from queue.
*/
- (void)saveLastOutgoingRmqId:(int64_t)rmqID {
[self.rmq2Store updateLastOutgoingRmqId:rmqID];
}
- (BOOL)saveS2dMessageWithRmqId:(NSString *)rmqID {
return [self.rmq2Store saveUnackedS2dMessageWithRmqId:rmqID];
}
#pragma mark - Query
- (int64_t)queryHighestRmqId {
return [self.rmq2Store queryHighestRmqId];
}
- (int64_t)querylastRmqId {
return [self.rmq2Store queryLastRmqId];
}
- (NSArray *)unackedS2dRmqIds {
return [self.rmq2Store unackedS2dRmqIds];
}
#pragma mark - FIRMessagingRMQScanner protocol
/**
* We don't have a 'getMessages' method - it would require loading in memory
* the entire content body of all messages.
*
* Instead we iterate and call 'resend' for each message.
*
* This is called:
* - on connect MCS, to resend any outstanding messages
* - init
*/
- (void)scanWithRmqMessageHandler:(FIRMessagingRmqMessageHandler)rmqMessageHandler
dataMessageHandler:(FIRMessagingDataMessageHandler)dataMessageHandler {
// no need to scan database with no callbacks
if (rmqMessageHandler || dataMessageHandler) {
[self.rmq2Store scanOutgoingRmqMessagesWithHandler:^(int64_t rmqId, int8_t tag, NSData *data) {
if (rmqMessageHandler != nil) {
rmqMessageHandler(rmqId, tag, data);
}
if (dataMessageHandler != nil && kFIRMessagingProtoTagDataMessageStanza == tag) {
GPBMessage *proto =
[FIRMessagingGetClassForTag((FIRMessagingProtoTag)tag) parseFromData:data error:NULL];
GtalkDataMessageStanza *stanza = (GtalkDataMessageStanza *)proto;
dataMessageHandler(rmqId, stanza);
}
}];
}
}
#pragma mark - Remove
- (void)ackReceivedForRmqId:(NSString *)rmqId {
// TODO: Optional book-keeping
}
- (int)removeRmqMessagesWithRmqId:(NSString *)rmqId {
return [self removeRmqMessagesWithRmqIds:@[rmqId]];
}
- (int)removeRmqMessagesWithRmqIds:(NSArray *)rmqIds {
if (![rmqIds count]) {
return 0;
}
for (NSString *rmqId in rmqIds) {
[self ackReceivedForRmqId:rmqId];
}
int64_t maxRmqId = -1;
for (NSString *rmqId in rmqIds) {
int64_t rmqIdValue = [rmqId longLongValue];
if (rmqIdValue > maxRmqId) {
maxRmqId = rmqIdValue;
}
}
maxRmqId++;
if (maxRmqId >= self.rmqId) {
[self saveLastOutgoingRmqId:maxRmqId];
}
return [self.rmq2Store deleteMessagesFromTable:kTableOutgoingRmqMessages withRmqIds:rmqIds];
}
- (void)removeS2dIds:(NSArray *)s2dIds {
[self.rmq2Store deleteMessagesFromTable:kTableS2DRmqIds withRmqIds:s2dIds];
}
#pragma mark - Sync Messages
// TODO: RMQManager should also have a cache for all the sync messages
// so we don't hit the DB each time.
- (FIRMessagingPersistentSyncMessage *)querySyncMessageWithRmqID:(NSString *)rmqID {
return [self.rmq2Store querySyncMessageWithRmqID:rmqID];
}
- (BOOL)deleteSyncMessageWithRmqID:(NSString *)rmqID {
return [self.rmq2Store deleteSyncMessageWithRmqID:rmqID];
}
- (int)deleteExpiredOrFinishedSyncMessages:(NSError **)error {
return [self.rmq2Store deleteExpiredOrFinishedSyncMessages:error];
}
- (BOOL)saveSyncMessageWithRmqID:(NSString *)rmqID
expirationTime:(int64_t)expirationTime
apnsReceived:(BOOL)apnsReceived
mcsReceived:(BOOL)mcsReceived
error:(NSError *__autoreleasing *)error {
return [self.rmq2Store saveSyncMessageWithRmqID:rmqID
expirationTime:expirationTime
apnsReceived:apnsReceived
mcsReceived:mcsReceived
error:error];
}
- (BOOL)updateSyncMessageViaAPNSWithRmqID:(NSString *)rmqID error:(NSError **)error {
return [self.rmq2Store updateSyncMessageViaAPNSWithRmqID:rmqID error:error];
}
- (BOOL)updateSyncMessageViaMCSWithRmqID:(NSString *)rmqID error:(NSError **)error {
return [self.rmq2Store updateSyncMessageViaMCSWithRmqID:rmqID error:error];
}
#pragma mark - Testing
+ (void)removeDatabaseWithName:(NSString *)dbName {
[FIRMessagingRmq2PersistentStore removeDatabase:dbName];
}
#pragma mark - Private
- (int64_t)nextRmqId {
return ++self.rmqId;
}
@end
/*
* Copyright 2017 Google
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#import <Foundation/Foundation.h>
typedef NS_ENUM(NSUInteger, FIRMessagingSecureSocketState){
kFIRMessagingSecureSocketNotOpen = 0,
kFIRMessagingSecureSocketOpening,
kFIRMessagingSecureSocketOpen,
kFIRMessagingSecureSocketClosing,
kFIRMessagingSecureSocketClosed,
kFIRMessagingSecureSocketError
};
@class FIRMessagingSecureSocket;
@protocol FIRMessagingSecureSocketDelegate<NSObject>
- (void)secureSocket:(FIRMessagingSecureSocket *)socket
didReceiveData:(NSData *)data
withTag:(int8_t)tag;
- (void)secureSocket:(FIRMessagingSecureSocket *)socket
didSendProtoWithTag:(int8_t)tag
rmqId:(NSString *)rmqId;
- (void)secureSocketDidConnect:(FIRMessagingSecureSocket *)socket;
- (void)didDisconnectWithSecureSocket:(FIRMessagingSecureSocket *)socket;
@end
/**
* This manages the input/output streams connected to the MCS server. Used to receive data from
* the server and send to it over the wire.
*/
@interface FIRMessagingSecureSocket : NSObject
@property(nonatomic, readwrite, weak) id<FIRMessagingSecureSocketDelegate> delegate;
@property(nonatomic, readonly, assign) FIRMessagingSecureSocketState state;
- (void)connectToHost:(NSString *)host port:(NSUInteger)port onRunLoop:(NSRunLoop *)runLoop;
- (void)disconnect;
- (void)sendData:(NSData *)data withTag:(int8_t)tag rmqId:(NSString *)rmqId;
@end
/*
* Copyright 2017 Google
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#import <Foundation/Foundation.h>
@class FIRMessagingRmqManager;
/**
* Handle sync messages being received both via MCS and APNS.
*/
@interface FIRMessagingSyncMessageManager : NSObject
/**
* Initialize sync message manager.
*
* @param rmqManager The RMQ manager on the client.
*
* @return Sync message manager.
*/
- (instancetype)initWithRmqManager:(FIRMessagingRmqManager *)rmqManager;
/**
* Remove expired sync message from persistent store. Also removes messages that have
* been received both via APNS and MCS.
*/
- (void)removeExpiredSyncMessages;
/**
* App did recive a sync message via APNS.
*
* @param message The sync message received.
*
* @return YES if the message is a duplicate of an already received sync message else NO.
*/
- (BOOL)didReceiveAPNSSyncMessage:(NSDictionary *)message;
/**
* App did receive a sync message via MCS.
*
* @param message The sync message received.
*
* @return YES if the message is a duplicate of an already received sync message else NO.
*/
- (BOOL)didReceiveMCSSyncMessage:(NSDictionary *)message;
@end
/*
* Copyright 2017 Google
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#import "FIRMessagingSyncMessageManager.h"
#import "FIRMessagingConstants.h"
#import "FIRMessagingDefines.h"
#import "FIRMessagingLogger.h"
#import "FIRMessagingPersistentSyncMessage.h"
#import "FIRMessagingRmqManager.h"
#import "FIRMessagingUtilities.h"
static const int64_t kDefaultSyncMessageTTL = 4 * 7 * 24 * 60 * 60; // 4 weeks
// 4 MB of free space is required to persist Sync messages
static const uint64_t kMinFreeDiskSpaceInMB = 1;
@interface FIRMessagingSyncMessageManager()
@property(nonatomic, readwrite, strong) FIRMessagingRmqManager *rmqManager;
@end
@implementation FIRMessagingSyncMessageManager
- (instancetype)init {
FIRMessagingInvalidateInitializer();
}
- (instancetype)initWithRmqManager:(FIRMessagingRmqManager *)rmqManager {
_FIRMessagingDevAssert(rmqManager, @"Invalid nil rmq manager while initalizing sync message manager");
self = [super init];
if (self) {
_rmqManager = rmqManager;
}
return self;
}
- (void)removeExpiredSyncMessages {
NSError *error;
int deleteCount = [self.rmqManager deleteExpiredOrFinishedSyncMessages:&error];
if (error) {
FIRMessagingLoggerError(kFIRMessagingMessageCodeSyncMessageManager000,
@"Error while deleting expired sync messages %@", error);
} else if (deleteCount > 0) {
FIRMessagingLoggerDebug(kFIRMessagingMessageCodeSyncMessageManager001,
@"Successfully deleted %d sync messages from store", deleteCount);
}
}
- (BOOL)didReceiveAPNSSyncMessage:(NSDictionary *)message {
return [self didReceiveSyncMessage:message viaAPNS:YES viaMCS:NO];
}
- (BOOL)didReceiveMCSSyncMessage:(NSDictionary *)message {
return [self didReceiveSyncMessage:message viaAPNS:NO viaMCS:YES];
}
- (BOOL)didReceiveSyncMessage:(NSDictionary *)message
viaAPNS:(BOOL)viaAPNS
viaMCS:(BOOL)viaMCS {
NSString *rmqID = message[kFIRMessagingMessageIDKey];
_FIRMessagingDevAssert([rmqID length], @"Invalid nil rmqID for message");
if (![rmqID length]) {
FIRMessagingLoggerError(kFIRMessagingMessageCodeSyncMessageManager002,
@"Invalid nil rmqID for sync message.");
return NO;
}
FIRMessagingPersistentSyncMessage *persistentMessage =
[self.rmqManager querySyncMessageWithRmqID:rmqID];
NSError *error;
if (!persistentMessage) {
// Do not persist the new message if we don't have enough disk space
uint64_t freeDiskSpace = FIRMessagingGetFreeDiskSpaceInMB();
if (freeDiskSpace < kMinFreeDiskSpaceInMB) {
return NO;
}
int64_t expirationTime = [[self class] expirationTimeForSyncMessage:message];
if (![self.rmqManager saveSyncMessageWithRmqID:rmqID
expirationTime:expirationTime
apnsReceived:viaAPNS
mcsReceived:viaMCS
error:&error]) {
FIRMessagingLoggerError(kFIRMessagingMessageCodeSyncMessageManager003,
@"Failed to save sync message with rmqID %@", rmqID);
} else {
FIRMessagingLoggerInfo(kFIRMessagingMessageCodeSyncMessageManager004,
@"Added sync message to cache: %@", rmqID);
}
return NO;
}
if (viaAPNS && !persistentMessage.apnsReceived) {
persistentMessage.apnsReceived = YES;
if (![self.rmqManager updateSyncMessageViaAPNSWithRmqID:rmqID error:&error]) {
FIRMessagingLoggerError(kFIRMessagingMessageCodeSyncMessageManager005,
@"Failed to update APNS state for sync message %@", rmqID);
}
} else if (viaMCS && !persistentMessage.mcsReceived) {
persistentMessage.mcsReceived = YES;
if (![self.rmqManager updateSyncMessageViaMCSWithRmqID:rmqID error:&error]) {
FIRMessagingLoggerError(kFIRMessagingMessageCodeSyncMessageManager006,
@"Failed to update MCS state for sync message %@", rmqID);
}
}
// Received message via both ways we can safely delete it.
if (persistentMessage.apnsReceived && persistentMessage.mcsReceived) {
if (![self.rmqManager deleteSyncMessageWithRmqID:rmqID]) {
FIRMessagingLoggerError(kFIRMessagingMessageCodeSyncMessageManager007,
@"Failed to delete sync message %@", rmqID);
} else {
FIRMessagingLoggerInfo(kFIRMessagingMessageCodeSyncMessageManager008,
@"Successfully deleted sync message from cache %@", rmqID);
}
}
// Already received this message either via MCS or APNS.
return YES;
}
+ (int64_t)expirationTimeForSyncMessage:(NSDictionary *)message {
int64_t ttl = kDefaultSyncMessageTTL;
if (message[kFIRMessagingMessageSyncMessageTTLKey]) {
ttl = [message[kFIRMessagingMessageSyncMessageTTLKey] longLongValue];
}
int64_t currentTime = FIRMessagingCurrentTimestampInSeconds();
return currentTime + ttl;
}
@end
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment