Skip to content

Fix #43 by making the PusherWebsocketReactNative class a singleton #79

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
May 19, 2023
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 41 additions & 33 deletions ios/PusherWebsocketReactNative.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ import Foundation

@objc(PusherWebsocketReactNative)
@objcMembers class PusherWebsocketReactNative: RCTEventEmitter, PusherDelegate, Authorizer {
private var pusher: Pusher!
private static var shared: PusherWebsocketReactNative!
private static var pusher: Pusher!

private var authorizerCompletionHandlers = [String: ([String:String]) -> Void]()
private var authorizerCompletionHandlerTimeout = 10 // seconds
Expand All @@ -12,6 +13,12 @@ import Foundation
private let authErrorType = "AuthError"
private let pusherEventPrefix = "PusherReactNative"

override init() {
super.init()

PusherWebsocketReactNative.shared = self
}

override func supportedEvents() -> [String]! {
return ["\(pusherEventPrefix):onConnectionStateChange",
"\(pusherEventPrefix):onSubscriptionError",
Expand All @@ -26,18 +33,19 @@ import Foundation

private func callback(name:String, body:Any) -> Void {
let pusherEventname = "\(pusherEventPrefix):\(name)"
self.sendEvent(withName:pusherEventname, body:body)
PusherWebsocketReactNative.shared.sendEvent(withName:pusherEventname, body:body)
}

func initialize(_ args:[String: Any], resolve:RCTPromiseResolveBlock,reject:RCTPromiseRejectBlock) {
if pusher != nil {
pusher.disconnect()
if PusherWebsocketReactNative.pusher != nil {
PusherWebsocketReactNative.pusher.unsubscribeAll()
PusherWebsocketReactNative.pusher.disconnect()
}
var authMethod:AuthMethod = .noMethod
if args["authEndpoint"] is String {
authMethod = .endpoint(authEndpoint: args["authEndpoint"] as! String)
} else if args["authorizer"] is Bool {
authMethod = .authorizer(authorizer: self)
authMethod = .authorizer(authorizer: PusherWebsocketReactNative.shared)
}
var host:PusherHost = .defaultHost
if args["host"] is String {
Expand Down Expand Up @@ -77,23 +85,23 @@ import Foundation
useTLS: useTLS,
activityTimeout: activityTimeout
)
pusher = Pusher(key: args["apiKey"] as! String, options: options)
PusherWebsocketReactNative.pusher = Pusher(key: args["apiKey"] as! String, options: options)
if args["maxReconnectionAttempts"] is Int {
pusher.connection.reconnectAttemptsMax = (args["maxReconnectionAttempts"] as! Int)
PusherWebsocketReactNative.pusher.connection.reconnectAttemptsMax = (args["maxReconnectionAttempts"] as! Int)
}
if args["maxReconnectGapInSeconds"] is TimeInterval {
pusher.connection.maxReconnectGapInSeconds = (args["maxReconnectGapInSeconds"] as! TimeInterval)
PusherWebsocketReactNative.pusher.connection.maxReconnectGapInSeconds = (args["maxReconnectGapInSeconds"] as! TimeInterval)
}
if args["pongTimeout"] is Int {
pusher.connection.pongResponseTimeoutInterval = args["pongTimeout"] as! TimeInterval / 1000.0
PusherWebsocketReactNative.pusher.connection.pongResponseTimeoutInterval = args["pongTimeout"] as! TimeInterval / 1000.0
}

if let authorizerTimeoutInSeconds = args["authorizerTimeoutInSeconds"] as? Int {
self.authorizerCompletionHandlerTimeout = authorizerTimeoutInSeconds
PusherWebsocketReactNative.shared.authorizerCompletionHandlerTimeout = authorizerTimeoutInSeconds
}

pusher.connection.delegate = self
pusher.bind(eventCallback: onEvent)
PusherWebsocketReactNative.pusher.connection.delegate = PusherWebsocketReactNative.shared
PusherWebsocketReactNative.pusher.bind(eventCallback: onEvent)
resolve(nil)
}

Expand All @@ -102,7 +110,7 @@ import Foundation
}

public func fetchAuthValue(socketID: String, channelName: String, completionHandler: @escaping (PusherAuth?) -> Void) {
self.callback(name:"onAuthorizer", body: [
PusherWebsocketReactNative.shared.callback(name:"onAuthorizer", body: [
"socketId": socketID,
"channelName": channelName
])
Expand All @@ -118,9 +126,9 @@ import Foundation
authorizerCompletionHandlers[key] = authCallback

// the JS thread might not call onAuthorizer – we need to cleanup the completion handler after timeout
let timeout = DispatchTimeInterval.seconds(self.authorizerCompletionHandlerTimeout)
let timeout = DispatchTimeInterval.seconds(PusherWebsocketReactNative.shared.authorizerCompletionHandlerTimeout)
DispatchQueue.main.asyncAfter(deadline: .now() + timeout) {
if let storedAuthHandler = self.authorizerCompletionHandlers.removeValue(forKey: key) {
if let storedAuthHandler = PusherWebsocketReactNative.shared.authorizerCompletionHandlers.removeValue(forKey: key) {
storedAuthHandler(["auth": "<authorizer_timeout>:error"])
}
}
Expand All @@ -132,9 +140,9 @@ import Foundation
storedAuthHandler(data)
}
}

public func changedConnectionState(from old: ConnectionState, to new: ConnectionState) {
self.callback(name:"onConnectionStateChange", body:[
PusherWebsocketReactNative.shared.callback(name:"onConnectionStateChange", body:[
"previousState": old.stringValue(),
"currentState": new.stringValue()
])
Expand All @@ -156,7 +164,7 @@ import Foundation
type = authErrorType
}

self.callback(name:"onSubscriptionError", body:[
PusherWebsocketReactNative.shared.callback(name:"onSubscriptionError", body:[
"message": (error != nil) ? error!.localizedDescription : ((data != nil) ? data! : error.debugDescription),
"type": type,
"code": code,
Expand All @@ -165,7 +173,7 @@ import Foundation
}

public func receivedError(error: PusherError) {
self.callback(
PusherWebsocketReactNative.shared.callback(
name:"onError", body:[
"message": error.message,
"code": error.code ?? -1,
Expand All @@ -175,7 +183,7 @@ import Foundation
}

public func failedToDecryptEvent(eventName: String, channelName: String, data: String?) {
self.callback(
PusherWebsocketReactNative.shared.callback(
name:"onDecryptionFailure", body:[
"eventName": eventName,
"reason": data
Expand All @@ -184,29 +192,29 @@ import Foundation
}

public func connect(_ resolve:RCTPromiseResolveBlock,reject:RCTPromiseRejectBlock) {
pusher.connect()
PusherWebsocketReactNative.pusher.connect()
resolve(nil)
}

public func disconnect(_ resolve:RCTPromiseResolveBlock,reject:RCTPromiseRejectBlock) {
pusher.disconnect()
PusherWebsocketReactNative.pusher.disconnect()
resolve(nil)
}

public func getSocketId() -> String? {
return pusher.connection.socketId
return PusherWebsocketReactNative.pusher.connection.socketId
}

func onEvent(event:PusherEvent) {
var userId:String? = nil
var mappedEventName:String? = nil
if event.eventName == "pusher:subscription_succeeded" {
if let channel = pusher.connection.channels.findPresence(name: event.channelName!) {
if let channel = PusherWebsocketReactNative.pusher.connection.channels.findPresence(name: event.channelName!) {
userId = channel.myId
}
mappedEventName = "pusher_internal:subscription_succeeded"
}
self.callback(
PusherWebsocketReactNative.shared.callback(
name:"onEvent",body:[
"channelName": event.channelName,
"eventName": mappedEventName ?? event.eventName,
Expand All @@ -219,25 +227,25 @@ import Foundation
func subscribe(_ channelName:String, resolve:RCTPromiseResolveBlock,reject:RCTPromiseRejectBlock) {
if channelName.hasPrefix("presence-") {
let onMemberAdded:(PusherPresenceChannelMember) -> () = { user in
self.callback(name:"onMemberAdded", body: [
PusherWebsocketReactNative.shared.callback(name:"onMemberAdded", body: [
"channelName": channelName,
"user": ["userId": user.userId, "userInfo": user.userInfo ]
])
}
let onMemberRemoved:(PusherPresenceChannelMember) -> () = { user in
self.callback(name:"onMemberRemoved", body: [
PusherWebsocketReactNative.shared.callback(name:"onMemberRemoved", body: [
"channelName": channelName,
"user": ["userId": user.userId, "userInfo": user.userInfo ]
])
}
pusher.subscribeToPresenceChannel(
PusherWebsocketReactNative.pusher.subscribeToPresenceChannel(
channelName: channelName,
onMemberAdded: onMemberAdded,
onMemberRemoved: onMemberRemoved
)
} else {
let onSubscriptionCount:(Int) -> () = { subscriptionCount in
self.callback(
PusherWebsocketReactNative.shared.callback(
name:"onEvent",body:[
"channelName": channelName,
"eventName": "pusher_internal:subscription_count",
Expand All @@ -248,19 +256,19 @@ import Foundation
]
)
}
pusher.subscribe(channelName: channelName,
onSubscriptionCountChanged: onSubscriptionCount)
PusherWebsocketReactNative.pusher.subscribe(channelName: channelName,
onSubscriptionCountChanged: onSubscriptionCount)
}
resolve(nil)
}

func unsubscribe(_ channelName:String, resolve:RCTPromiseResolveBlock,reject:RCTPromiseRejectBlock) {
pusher.unsubscribe(channelName)
PusherWebsocketReactNative.pusher.unsubscribe(channelName)
resolve(nil)
}

func trigger(_ channelName:String, eventName:String, data:Any, resolve:RCTPromiseResolveBlock,reject:RCTPromiseRejectBlock) {
if let channel = pusher.connection.channels.find(name: channelName) {
if let channel = PusherWebsocketReactNative.pusher.connection.channels.find(name: channelName) {
channel.trigger(eventName: eventName, data: data)
}
resolve(nil)
Expand Down