Skip to content

Commit

Permalink
onAuthorizer: remove mutex locks in favor of storing callbacks (#68)
Browse files Browse the repository at this point in the history
* Remove mutex locks in favor of storing callbacks so onAuthorizer does no longer freeze the app on iOS

* Update README with authorizerTimeoutInSeconds attribute

* Fix lint issues

* Bump to version 1.2.0


Co-authored-by: Pusher CI <[email protected]>
  • Loading branch information
fbenevides and pusher-ci authored Jan 19, 2023
1 parent 3d1e0e4 commit 393b112
Show file tree
Hide file tree
Showing 7 changed files with 97 additions and 41 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Changelog

## 1.2.0

* [CHANGED] Remove mutex locks in favor of storing callbacks so onAuthorizer does no longer freeze the app on iOS

## 1.1.1

* [CHANGED] Allow re-init of the Pusher singleton.
Expand Down
32 changes: 19 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -153,17 +153,18 @@ You can subscribe to channels before calling `connect()`.
There are a few configuration parameters which can be set for the Pusher client. The following table
describes available parameters for each platform:

| parameter | Android | iOS |
| ------------------------ | ------- | --- |
| activityTimeout |||
| apiKey |||
| authEndpoint |||
| cluster |||
| maxReconnectGapInSeconds |||
| maxReconnectionAttempts |||
| pongTimeout |||
| proxy || ⬜️ |
| useTLS |||
| parameter | Android | iOS |
| -------------------------- | ------- | --- |
| activityTimeout |||
| apiKey |||
| authEndpoint |||
| cluster |||
| maxReconnectGapInSeconds |||
| maxReconnectionAttempts |||
| pongTimeout |||
| proxy || ⬜️ |
| useTLS |||
| authorizerTimeoutInSeconds | ⬜️ ||

#### `activityTimeout (double)`

Expand All @@ -188,6 +189,11 @@ Specifies the cluster that pusher-js should connect to. Here's the full list of

Whether or not you would like to use TLS encrypted transport or not, default is `true`.

#### `authorizerTimeoutInSeconds (double)`

If onAuthorizer callback is not called in Javascript before this time period (in seconds), the authorization for the channel will timeout on the native side. Default value: 10 seconds. iOS only.


## Event Callback parameters

The following functions are callbacks that can be passed to the `init()` method. All are optional.
Expand Down Expand Up @@ -269,7 +275,7 @@ When passing the `onAuthorizer()` callback to the `init()` method, this callback
to [generate the correct auth signatures](https://pusher.com/docs/channels/library_auth_reference/auth-signatures/)

```typescript
async function onAuthorizer(channelName:string, socketId:string):Promise<any> {
async function onAuthorizer(channelName:string, socketId:string):Promise<PusherAuthorizerResult> {
return {
auth: "foo:bar",
channel_data: '{"user_id": 1}',
Expand Down Expand Up @@ -562,4 +568,4 @@ Pusher is owned and maintained by [Pusher](https://pusher.com).
## License
Pusher is released under the MIT license. Refer to [LICENSE](https://github.com/pusher/pusher-websocket-react-native/blob/master/LICENSE) for more details.
Pusher is released under the MIT license. Refer to [LICENSE](https://github.com/pusher/pusher-websocket-react-native/blob/master/LICENSE) for more details.
8 changes: 4 additions & 4 deletions example/ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ PODS:
- libevent (2.1.12)
- NWWebSocket (0.5.2)
- OpenSSL-Universal (1.1.1100)
- pusher-websocket-react-native (1.1.0):
- pusher-websocket-react-native (1.1.1):
- PusherSwift (~> 10.1.1)
- React
- PusherSwift (10.1.1):
Expand Down Expand Up @@ -544,7 +544,7 @@ EXTERNAL SOURCES:
SPEC CHECKSUMS:
boost: a7c83b31436843459a1961bfd74b96033dc77234
CocoaAsyncSocket: 065fd1e645c7abab64f7a6a2007a48038fdc6a99
DoubleConversion: 5189b271737e1565bdce30deb4a08d647e3f5f54
DoubleConversion: 831926d9b8bf8166fd87886c4abab286c2422662
FBLazyVector: affa4ba1bfdaac110a789192f4d452b053a86624
FBReactNativeSpec: fe8b5f1429cfe83a8d72dc8ed61dc7704cac8745
Flipper: 26fc4b7382499f1281eb8cb921e5c3ad6de91fe0
Expand All @@ -557,12 +557,12 @@ SPEC CHECKSUMS:
Flipper-RSocket: d9d9ade67cbecf6ac10730304bf5607266dd2541
FlipperKit: cbdee19bdd4e7f05472a66ce290f1b729ba3cb86
fmt: ff9d55029c625d3757ed641535fd4a75fedc7ce9
glog: 04b94705f318337d7ead9e6d17c019bd9b1f6b1b
glog: 476ee3e89abb49e07f822b48323c51c57124b572
hermes-engine: 7fe5fc6ef707b7fdcb161b63898ec500e285653d
libevent: 4049cae6c81cdb3654a443be001fb9bdceff7913
NWWebSocket: 21f0c73639815da3272862c912275b26102aa80c
OpenSSL-Universal: ebc357f1e6bc71fa463ccb2fe676756aff50e88c
pusher-websocket-react-native: e3b93548fc89a85e79d68ace4c6e8a7eca80315f
pusher-websocket-react-native: f39fffc44df8914ca5c9382a0acb446130d9946e
PusherSwift: 70a805a950ab49381e164c54ac19c3b9c1d288e3
RCT-Folly: 0080d0a6ebf2577475bda044aa59e2ca1f909cda
RCTRequired: 21229f84411088e5d8538f21212de49e46cc83e2
Expand Down
41 changes: 28 additions & 13 deletions example/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import * as React from 'react';

import {
StyleSheet,
View,
Expand All @@ -11,8 +12,13 @@ import {
FlatList,
} from 'react-native';
import AsyncStorage from '@react-native-async-storage/async-storage';
import CryptoES from 'crypto-es';
import { Pusher, PusherMember, PusherChannel, PusherEvent } from '../../src'; // This links the example app to the current SDK implementation
import {
Pusher,
PusherMember,
PusherChannel,
PusherEvent,
PusherAuthorizerResult,
} from '../../src'; // This links the example app to the current SDK implementation

export default function App() {
let logLines: string[] = [];
Expand Down Expand Up @@ -149,17 +155,26 @@ export default function App() {
};

// See https://pusher.com/docs/channels/library_auth_reference/auth-signatures/ for the format of this object.
const onAuthorizer = (channelName: string, socketId: string) => {
const user = JSON.stringify({ user_id: 12345 });
const stringToSign = socketId + ':' + channelName + ':' + user;
const pusherKey = '<YOUR PUSHER KEY>';
const pusherSecret = '<YOUR PUSHER SECRET>';
const signature = CryptoES.HmacSHA256(stringToSign, pusherSecret);
return {
auth: pusherKey + ':' + signature,
channel_data: user,
shared_secret: 'foobar',
};
const onAuthorizer = async (channelName: string, socketId: string) => {
log(
`calling onAuthorizer. channelName=${channelName}, socketId=${socketId}`
);

const response = await fetch('some_url', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
socket_id: socketId,
channel_name: channelName,
}),
});

const body = (await response.json()) as PusherAuthorizerResult;

log(`response: ${JSON.stringify(body)}`);
return body;
};

const trigger = async () => {
Expand Down
35 changes: 26 additions & 9 deletions ios/PusherWebsocketReactNative.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ import Foundation
@objcMembers class PusherWebsocketReactNative: RCTEventEmitter, PusherDelegate, Authorizer {
private var pusher: Pusher!

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

private let subscriptionErrorType = "SubscriptionError"
private let authErrorType = "AuthError"
Expand Down Expand Up @@ -85,6 +85,11 @@ import Foundation
if args["pongTimeout"] is Int {
pusher.connection.pongResponseTimeoutInterval = args["pongTimeout"] as! TimeInterval / 1000.0
}

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

pusher.connection.delegate = self
pusher.bind(eventCallback: onEvent)
resolve(nil)
Expand All @@ -101,17 +106,29 @@ import Foundation
])

let key = channelName + socketID
authorizerMutex[key] = DispatchSemaphore(value: 0)
authorizerMutex[key]!.wait()
let authParams = authorizerResult.removeValue(forKey: key)!
completionHandler(PusherAuth(auth: authParams["auth"]!, channelData: authParams["channel_data"], sharedSecret: authParams["shared_secret"]))
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(self.authorizerCompletionHandlerTimeout)
DispatchQueue.main.asyncAfter(deadline: .now() + timeout) {
if let storedAuthHandler = self.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
authorizerResult[key] = data
authorizerMutex[key]!.signal()
authorizerMutex.removeValue(forKey: key)
if let storedAuthHandler = authorizerCompletionHandlers.removeValue(forKey: key) {
storedAuthHandler(data)
}
}


Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@pusher/pusher-websocket-react-native",
"version": "1.1.1",
"version": "1.2.0",
"description": "Pusher Channels Client for React Native",
"main": "lib/commonjs/index",
"module": "lib/module/index",
Expand Down
16 changes: 15 additions & 1 deletion src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,15 @@ const PusherWebsocketReactNative = NativeModules.PusherWebsocketReactNative
}
);

export interface PusherAuthorizerResult {
/** required for private channels */
auth?: string;
/** required for encrypted channels */
shared_secret?: string;
/** required for presence channels, should be stringified JSON */
channel_data?: string;
}

export class PusherEvent {
channelName: string;
eventName: string;
Expand Down Expand Up @@ -124,12 +133,16 @@ export class Pusher {
pongTimeout?: Number;
maxReconnectionAttempts?: Number;
maxReconnectGapInSeconds?: Number;
authorizerTimeoutInSeconds?: Number;
proxy?: string;
onConnectionStateChange?: (
currentState: string,
previousState: string
) => void;
onAuthorizer?: (channelName: string, socketId: string) => any;
onAuthorizer?: (
channelName: string,
socketId: string
) => Promise<PusherAuthorizerResult>;
onError?: (message: string, code: Number, e: any) => void;
onEvent?: (event: PusherEvent) => void;
onSubscriptionSucceeded?: (channelName: string, data: any) => void;
Expand Down Expand Up @@ -245,6 +258,7 @@ export class Pusher {
pongTimeout: args.pongTimeout,
maxReconnectionAttempts: args.maxReconnectionAttempts,
maxReconnectGapInSeconds: args.maxReconnectGapInSeconds,
authorizerTimeoutInSeconds: args.authorizerTimeoutInSeconds,
authorizer: args.onAuthorizer ? true : false,
proxy: args.proxy,
});
Expand Down

0 comments on commit 393b112

Please sign in to comment.