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

Conversation

evrimfeyyaz
Copy link
Contributor

Description

During development, this library makes the app crash when the app receives an event from this library after a reload (demonstrated in #43).

This PR solves the issue by making the PusherWebsocketReactNative a singleton.

The pusher instance within the class is also made static, otherwise init calls from the JS side creates duplicate event subscriptions.

CHANGELOG

@fbenevides fbenevides added the bug Something isn't working label Feb 27, 2023
@swaaj
Copy link

swaaj commented Mar 12, 2023

I confirm this PR seems to fix the issue for me. When will this PR be merged?

@jacobmolby
Copy link

jacobmolby commented Mar 15, 2023

It seems to fix it for me also. Thanks @evrimfeyyaz!

Here is the patch if anyone needs it (@pusher+pusher-websocket-react-native+1.2.1.patch):

diff --git a/node_modules/@pusher/pusher-websocket-react-native/ios/PusherWebsocketReactNative.swift b/node_modules/@pusher/pusher-websocket-react-native/ios/PusherWebsocketReactNative.swift
index 42797fe..07863ea 100644
--- a/node_modules/@pusher/pusher-websocket-react-native/ios/PusherWebsocketReactNative.swift
+++ b/node_modules/@pusher/pusher-websocket-react-native/ios/PusherWebsocketReactNative.swift
@@ -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
@@ -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",
@@ -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 {
@@ -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)
     }
 
@@ -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
         ])
@@ -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"])
             }
         }
@@ -134,7 +142,7 @@ import Foundation
     }
 
     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()
         ])
@@ -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,
@@ -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,
@@ -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
@@ -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,
@@ -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",
@@ -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)

@swaaj
Copy link

swaaj commented Mar 16, 2023

I confirm this PR seems to fix the issue for me. When will this PR be merged?

Finally, after some time, I have noticed that the crash still happens, even with this patch... :(

@fbenevides fbenevides merged commit 7b9c723 into pusher:master May 19, 2023
fbenevides added a commit that referenced this pull request Jul 26, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

Successfully merging this pull request may close these issues.

App crashing after reload
4 participants