Issue #377

OneSignal is an alternative for Parse for push notifications but the sdk has many extra stuff and assumptions and lots of swizzling.

We can just use Rest to make API calls. From https://github.com/onmyway133/Dust

Every official push notification SDK can do many things

  • Register device token. This is crucial for the notification to get from your backend -> APNS -> device
  • Manage player id, user id, arn, …This is used to associate with device token
  • Manager tag, topic, subscription, segments, …This is used to group a set of device tokens
  • Do swizzling, update your application badge number, change your user notification settings, … without your knowing about that
  • Some other fancy stuffs
  • Dust does only one thing, which is push notification handling. The rest is under your control

OneSignal

func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject : AnyObject]?) -> Bool {
  OneSignal.appID = ""
}

func application(application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: NSData) {
  OneSignal.handleDeviceToken(deviceToken)
}

Here is the implementation

import UIKit

struct Utils {

  static func parse(deviceToken data: NSData) -> String {
    let buffer = UnsafePointer<CChar>(data.bytes)
    var string = ""

    for i in 0..<data.length {
      string += String(format: "%02.2hhx", arguments: [buffer[i]])
    }

    return string
  }

  static func deviceModel() -> String {
    var systemInfo = utsname()
    uname(&systemInfo)
    var v = systemInfo.machine

    var deviceModel = ""
    let _ = withUnsafePointer(&v) {
      deviceModel = String(UTF8String: UnsafePointer($0)) ?? ""
    }

    return deviceModel
  }

  static func systemVersion() -> String {
    let version = NSProcessInfo.processInfo().operatingSystemVersion

    return "\(version.majorVersion).\(version.minorVersion).\(version.patchVersion)"
  }

  static func language() -> String {
    return NSLocale.preferredLanguages().first!
  }

  static func timezone() -> Int {
    return NSTimeZone.localTimeZone().secondsFromGMT
  }

  static func soundFiles() -> [String] {
    guard let resourcePath = NSBundle.mainBundle().resourcePath
      else { return [] }

    let files = try? NSFileManager.defaultManager()
      .contentsOfDirectoryAtPath(resourcePath)
      .filter {
        return $0.hasSuffix(".wav") || $0.hasSuffix(".mp3")
      }

    return files ?? []
  }

  static func versionNumber() -> String? {
    return NSBundle.mainBundle().infoDictionary?["CFBundleShortVersionString"] as? String
  }

  static func buildNumber() -> String? {
    return NSBundle.mainBundle().infoDictionary?["CFBundleVersionString"] as? String
  }

  static func netType() -> Int {
    // Reachability
    return 0
  }
}
import Foundation

public struct UserDefaults {

  struct Key {
    static let playerID: String = "Dust-OneSignal-Player-ID-Key"
    static let deviceToken: String = "Dust-OneSignal-Device-Token-Key"
    static let subscribed: String = "Dust-OneSignal-Disable-Subscribed-Key"
  }

  public static var playerID: String? {
    get {
      return NSUserDefaults.standardUserDefaults().stringForKey(Key.playerID)
    }

    set {
      NSUserDefaults.standardUserDefaults().setObject(newValue, forKey: Key.playerID)
      NSUserDefaults.standardUserDefaults().synchronize()
    }
  }

  public static var deviceToken: String? {
    get {
      return NSUserDefaults.standardUserDefaults().stringForKey(Key.deviceToken)
    }

    set {
      NSUserDefaults.standardUserDefaults().setObject(newValue, forKey: Key.deviceToken)
      NSUserDefaults.standardUserDefaults().synchronize()
    }
  }

  public static var subscribed: Bool {
    get {
      return NSUserDefaults.standardUserDefaults().boolForKey(Key.subscribed)
    }

    set {
      NSUserDefaults.standardUserDefaults().setBool(newValue, forKey: Key.subscribed)
      NSUserDefaults.standardUserDefaults().synchronize()
    }
  }
}
import Foundation
import Alamofire

public struct OneSignal {

  static var appID: String = ""
  static let version = "020115"
  static let baseURL = NSURL(string: "https://onesignal.com/api/v1")!

  enum NotificationType: Int {
    case subscribed = 7
    case unsubscribed = -2

    static func value() -> Int {
      return UserDefaults.subscribed
        ? NotificationType.subscribed.rawValue : NotificationType.unsubscribed.rawValue
    }
  }

  enum Provisioning: Int {
    case development = 1
  }

  public static func setup(appID appID: String) {
    NSUserDefaults.standardUserDefaults().registerDefaults([
      UserDefaults.Key.subscribed: true
    ])

    OneSignal.appID = appID
  }

  public static func registerOrUpdateSession(completion: ((String?) -> Void)? = nil) {
    guard let bundleID = NSBundle.mainBundle().bundleIdentifier,
      let deviceToken = UserDefaults.deviceToken
    else {
      return
    }

    var params: [String: AnyObject] = [
      "app_id" : appID,
      "device_model" : Utils.deviceModel(),
      "device_os" : Utils.systemVersion(),
      "language" : Utils.language(),
      "timezone" : NSNumber(integer: Utils.timezone()),
      "device_type" : NSNumber(integer : 0),
      "sounds" : Utils.soundFiles(),
      "sdk" : version,
      "identifier" : deviceToken,
      "net_type" : NSNumber(integer: Utils.netType()),
      "rooted": NSNumber(bool: false),
      "as_id": "OptedOut",
      "sdk_type": "native",
      "ios_bundle": bundleID,
      "game_version": Utils.versionNumber() ?? "",
      "notification_types": NotificationType.value(),
    ]

    #if DEBUG
      params["test_type"] = Provisioning.development.rawValue
    #endif

    let url: NSURL

    if let playerID = UserDefaults.playerID {
      url = baseURL.URLByAppendingPathComponent("players/\(playerID)/on_session")
    } else {
      url = baseURL.URLByAppendingPathComponent("players")
    }

    Alamofire
    .request(.POST, url, parameters: params)
    .responseJSON { response in
      guard let json = response.result.value as? [String: AnyObject]
      else {
        completion?(nil)
        return
      }

      if let id = json["id"] as? String {
        UserDefaults.playerID = id
        completion?(id)
      } else if let value = json["success"] as? Int,
        playerID = UserDefaults.playerID where value == 1 {
        completion?(playerID)
      } else {
        completion?(nil)
      }
    }
  }

  public static func handle(deviceToken data: NSData) {
    UserDefaults.deviceToken = Utils.parse(deviceToken: data)
    registerOrUpdateSession()
  }

  public static func update(subscription subscribed: Bool) {
    guard let playerID = UserDefaults.playerID else { return }
    UserDefaults.subscribed = subscribed

    let url = baseURL.URLByAppendingPathComponent("players/\(playerID)")
    let params: [String: AnyObject] = [
      "app_id": appID,
      "notification_types": NotificationType.value()
    ]

    Alamofire
    .request(.PUT, url, parameters: params)
    .responseJSON { response in
      print(response)
    }
  }

  public static func update(badge count: Int) {
    guard let playerID = UserDefaults.playerID else { return }

    let url = baseURL.URLByAppendingPathComponent("players/\(playerID)")
    let params: [String: AnyObject] = [
      "app_id": appID,
      "badge_count": count
    ]

    Alamofire
      .request(.PUT, url, parameters: params)
      .responseJSON { response in

    }
  }

  public static func getPlayerID(completion: String -> Void) {
    if let playerID = UserDefaults.playerID {
      completion(playerID)
      return
    }

    registerOrUpdateSession { playerID in
      if let playerID = playerID {
        completion(playerID)
      }
    }
  }
}