Skip to content
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

PusherWebsocketReactNative.swift ( IOS Application crash) #143

Open
rahul-s-1110 opened this issue Feb 16, 2024 · 1 comment
Open

PusherWebsocketReactNative.swift ( IOS Application crash) #143

rahul-s-1110 opened this issue Feb 16, 2024 · 1 comment

Comments

@rahul-s-1110
Copy link

rahul-s-1110 commented Feb 16, 2024

I'm getting an crash issue in IOS while pusher unsubscribing

PusherWebsocketReactNative.swift - Line 7
@objc PusherWebsocketReactNative.unsubscribe(_:resolve:reject:) + 7

Here's my PusherWebsocketReactNative.swift code:

import PusherSwift
import Foundation

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

private var authorizerCompletionHandlers = [String: ([String:String]) -> Void]()
private var authorizerCompletionHandlerTimeout = 10 // seconds

private let subscriptionErrorType = "SubscriptionError"
private let authErrorType = "AuthError"
private let pusherEventPrefix = "PusherReactNative"

override init() {
    super.init()

    PusherWebsocketReactNative.shared = self
}

override func supportedEvents() -> [String]! {
    return ["\(pusherEventPrefix):onConnectionStateChange",
            "\(pusherEventPrefix):onSubscriptionError",
            "\(pusherEventPrefix):onSubscriptionCount",
            "\(pusherEventPrefix):onAuthorizer",
            "\(pusherEventPrefix):onError",
            "\(pusherEventPrefix):onDecryptionFailure",
            "\(pusherEventPrefix):onEvent",
            "\(pusherEventPrefix):onMemberAdded",
            "\(pusherEventPrefix):onMemberRemoved"]
}

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

func initialize(_ args:[String: Any], resolve:RCTPromiseResolveBlock,reject:RCTPromiseRejectBlock) {
    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: PusherWebsocketReactNative.shared)
    }
    var host:PusherHost = .defaultHost
    if args["host"] is String {
        host = .host(args["host"] as! String)
    } else if args["cluster"] != nil {
        host = .cluster(args["cluster"] as! String)
    }
    var useTLS:Bool = true
    if args["useTLS"] is Bool {
        useTLS = args["useTLS"] as! Bool
    }
    var port:Int
    if useTLS {
        port = 443
        if args["wssPort"] is Int {
            port = args["wssPort"] as! Int
        }
    } else {
        port = 80
        if args["wsPort"] is Int {
            port = args["wsPort"] as! Int
        }
    }
    var activityTimeout:TimeInterval? = nil
    if args["activityTimeout"] is TimeInterval {
        activityTimeout = args["activityTimeout"] as! Double / 1000.0
    }
    var path:String? = nil
    if args["path"] is String {
        path = (args["path"] as! String)
    }
    let options = PusherClientOptions(
        authMethod: authMethod,
        host: host,
        port: port,
        path: path,
        useTLS: useTLS,
        activityTimeout: activityTimeout
    )
    PusherWebsocketReactNative.pusher = Pusher(key: args["apiKey"] as! String, options: options)
    if args["maxReconnectionAttempts"] is Int {
        PusherWebsocketReactNative.pusher.connection.reconnectAttemptsMax = (args["maxReconnectionAttempts"] as! Int)
    }
    if args["maxReconnectGapInSeconds"] is TimeInterval {
        PusherWebsocketReactNative.pusher.connection.maxReconnectGapInSeconds = (args["maxReconnectGapInSeconds"] as! TimeInterval)
    }
    if args["pongTimeout"] is Int {
        PusherWebsocketReactNative.pusher.connection.pongResponseTimeoutInterval = args["pongTimeout"] as! TimeInterval / 1000.0
    }

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

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

override static func requiresMainQueueSetup() -> Bool {
    return false
}

public func fetchAuthValue(socketID: String, channelName: String, completionHandler: @escaping (PusherAuth?) -> Void) {
    PusherWebsocketReactNative.shared.callback(name:"onAuthorizer", body: [
        "socketId": socketID,
        "channelName": channelName
    ])

    let key = channelName + socketID
    let authCallback = { (authParams:[String:String]) in
        if let authParam = authParams["auth"] {
            completionHandler(PusherAuth(auth: authParam, channelData: authParams["channel_data"], sharedSecret: authParams["shared_secret"]))
        } else {
            completionHandler(PusherAuth(auth: "<missing_auth_param>:error", channelData: authParams["channel_data"], sharedSecret: authParams["shared_secret"]))
        }
    }
    authorizerCompletionHandlers[key] = authCallback

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

public func onAuthorizer(_ channelName: String, socketID: String, data:[String:String], resolve:RCTPromiseResolveBlock,reject:RCTPromiseRejectBlock) {
    let key = channelName + socketID
    if let storedAuthHandler = authorizerCompletionHandlers.removeValue(forKey: key) {
        storedAuthHandler(data)
    }
}

public func changedConnectionState(from old: ConnectionState, to new: ConnectionState) {
    PusherWebsocketReactNative.shared.callback(name:"onConnectionStateChange", body:[
        "previousState": old.stringValue(),
        "currentState": new.stringValue()
    ])
}

public func debugLog(message: String) {
    //print("DEBUG:", message)
}

public func subscribedToChannel(name: String) {
    // Handled by global handler
}

public func failedToSubscribeToChannel(name: String, response: URLResponse?, data: String?, error: NSError?) {
    var code = ""
    var type = subscriptionErrorType
    if let httpResponse = response as? HTTPURLResponse {
        code = String(httpResponse.statusCode)
        type = authErrorType
    }

    PusherWebsocketReactNative.shared.callback(name:"onSubscriptionError", body:[
        "message": (error != nil) ? error!.localizedDescription : ((data != nil) ? data! : error.debugDescription),
        "type": type,
        "code": code,
        "channelName": name
    ])
}

public func receivedError(error: PusherError) {
    PusherWebsocketReactNative.shared.callback(
        name:"onError", body:[
            "message": error.message,
            "code": error.code ?? -1,
            "error": error.debugDescription
        ]
    )
}

public func failedToDecryptEvent(eventName: String, channelName: String, data: String?) {
    PusherWebsocketReactNative.shared.callback(
        name:"onDecryptionFailure", body:[
            "eventName": eventName,
            "reason": data
        ]
    )
}

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

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

public func getSocketId() -> String? {
    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 = PusherWebsocketReactNative.pusher.connection.channels.findPresence(name: event.channelName!) {
            userId = channel.myId
        }
        mappedEventName = "pusher_internal:subscription_succeeded"
    }
    PusherWebsocketReactNative.shared.callback(
        name:"onEvent",body:[
            "channelName": event.channelName,
            "eventName": mappedEventName ?? event.eventName,
            "userId": event.userId ?? userId,
            "data": event.data
        ]
    )
}

func subscribe(_ channelName:String, resolve:RCTPromiseResolveBlock,reject:RCTPromiseRejectBlock) {
    if channelName.hasPrefix("presence-") {
        let onMemberAdded:(PusherPresenceChannelMember) -> () = { user in
            PusherWebsocketReactNative.shared.callback(name:"onMemberAdded", body: [
                "channelName": channelName,
                "user": ["userId": user.userId, "userInfo": user.userInfo ]
            ])
        }
        let onMemberRemoved:(PusherPresenceChannelMember) -> () = { user in
            PusherWebsocketReactNative.shared.callback(name:"onMemberRemoved", body: [
                "channelName": channelName,
                "user": ["userId": user.userId, "userInfo": user.userInfo ]
            ])
        }
        PusherWebsocketReactNative.pusher.subscribeToPresenceChannel(
            channelName: channelName,
            onMemberAdded: onMemberAdded,
            onMemberRemoved: onMemberRemoved
        )
    } else {
        let onSubscriptionCount:(Int) -> () = { subscriptionCount in
            PusherWebsocketReactNative.shared.callback(
                name:"onEvent",body:[
                    "channelName": channelName,
                    "eventName": "pusher_internal:subscription_count",
                    "userId": nil,
                    "data": [
                        "subscription_count": subscriptionCount
                    ]
                ]
            )
        }
        PusherWebsocketReactNative.pusher.subscribe(channelName: channelName,
                                                    onSubscriptionCountChanged: onSubscriptionCount)
    }
    resolve(nil)
}

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

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

and here's our pusherDisconnect code in app.js file


useEffect(() => {
handleGetPusherSetting();
return async () => {
console.log("pusher in return app stack", pusher);
if (pusher !== null && pusher.connectionState !== "DISCONNECTED") {
console.log("inside pusher unsucribe setting")
await pusher.unsubscribe({
channelName: portal-${clientConfigs["app-claims/config-id"]}-${clientConfigs["http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier"]},
});
await pusher.disconnect();
} else {
const pusher = Pusher.getInstance();
await pusher.unsubscribe({
channelName: portal-${clientConfigs["app-claims/config-id"]}-${clientConfigs["http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier"]},
});
await pusher.disconnect();
}
};
}, []);

@benw-pusher
Copy link
Contributor

Can you share full error you are experiencing?

The channel name you are passing to the unsubscribe method is unusual. Is this correct channelName: portal-${clientConfigs["app-claims/config-id"]}-${clientConfigs["http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier"]},?

Is this resolving to the correct value?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants