Closed pearlopenxcell closed 6 months ago
Can you share more info about this, including the full stack trace and the code in use?
Hi, I think I reproduced the same issue. It seems to occur when trying to disconnect an uninitialized Pusher instance.
I encountered the following error:
pusher_websocket_react_native/PusherWebsocketReactNative.swift:200: Fatal error: Unexpectedly found nil while implicitly unwrapping an Optional value
I found a fix by performing the following check before disconnecting in the TypeScript code:
if ( pusher != null && pusher.connectionState !== "DISCONNECTED" ) {
pusher.disconnect()
}
Alternatively, another solution is to modify the PusherWebsocketReactNative.swift file as follows:
public func disconnect(_ resolve:RCTPromiseResolveBlock,reject:RCTPromiseRejectBlock) {
PusherWebsocketReactNative.pusher.disconnect()
resolve(nil)
}
to:
public func disconnect(_ resolve:RCTPromiseResolveBlock,reject:RCTPromiseRejectBlock) {
if PusherWebsocketReactNative.pusher != nil {
PusherWebsocketReactNative.pusher.disconnect()
}
resolve(nil)
}
@Gabfranck Thanks for sharing. Still Error has been coming now Error is been coming with code of @fbenevides @ekrembk @callaars @benw-pusher @evrimfeyyaz @essaji Could you please suggest a solution on It. As I'm stuck in mid between of this. 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();
}
};
}, []);
@rahul-s-1110 i can see you have logged a new issue at https://github.com/pusher/pusher-websocket-react-native/issues/143 for your report. It isn't clear if your issue is directly related to the original issue so we should progress the invesitgation in #143 .
Crash in iOS