Skip to content

Commit 393b112

Browse files
onAuthorizer: remove mutex locks in favor of storing callbacks (#68)
* 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]>
1 parent 3d1e0e4 commit 393b112

File tree

7 files changed

+97
-41
lines changed

7 files changed

+97
-41
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
# Changelog
22

3+
## 1.2.0
4+
5+
* [CHANGED] Remove mutex locks in favor of storing callbacks so onAuthorizer does no longer freeze the app on iOS
6+
37
## 1.1.1
48

59
* [CHANGED] Allow re-init of the Pusher singleton.

README.md

Lines changed: 19 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -153,17 +153,18 @@ You can subscribe to channels before calling `connect()`.
153153
There are a few configuration parameters which can be set for the Pusher client. The following table
154154
describes available parameters for each platform:
155155

156-
| parameter | Android | iOS |
157-
| ------------------------ | ------- | --- |
158-
| activityTimeout |||
159-
| apiKey |||
160-
| authEndpoint |||
161-
| cluster |||
162-
| maxReconnectGapInSeconds |||
163-
| maxReconnectionAttempts |||
164-
| pongTimeout |||
165-
| proxy || ⬜️ |
166-
| useTLS |||
156+
| parameter | Android | iOS |
157+
| -------------------------- | ------- | --- |
158+
| activityTimeout |||
159+
| apiKey |||
160+
| authEndpoint |||
161+
| cluster |||
162+
| maxReconnectGapInSeconds |||
163+
| maxReconnectionAttempts |||
164+
| pongTimeout |||
165+
| proxy || ⬜️ |
166+
| useTLS |||
167+
| authorizerTimeoutInSeconds | ⬜️ ||
167168

168169
#### `activityTimeout (double)`
169170

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

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

192+
#### `authorizerTimeoutInSeconds (double)`
193+
194+
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.
195+
196+
191197
## Event Callback parameters
192198

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

271277
```typescript
272-
async function onAuthorizer(channelName:string, socketId:string):Promise<any> {
278+
async function onAuthorizer(channelName:string, socketId:string):Promise<PusherAuthorizerResult> {
273279
return {
274280
auth: "foo:bar",
275281
channel_data: '{"user_id": 1}',
@@ -562,4 +568,4 @@ Pusher is owned and maintained by [Pusher](https://pusher.com).
562568
563569
## License
564570
565-
Pusher is released under the MIT license. Refer to [LICENSE](https://github.com/pusher/pusher-websocket-react-native/blob/master/LICENSE) for more details.
571+
Pusher is released under the MIT license. Refer to [LICENSE](https://github.com/pusher/pusher-websocket-react-native/blob/master/LICENSE) for more details.

example/ios/Podfile.lock

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ PODS:
7777
- libevent (2.1.12)
7878
- NWWebSocket (0.5.2)
7979
- OpenSSL-Universal (1.1.1100)
80-
- pusher-websocket-react-native (1.1.0):
80+
- pusher-websocket-react-native (1.1.1):
8181
- PusherSwift (~> 10.1.1)
8282
- React
8383
- PusherSwift (10.1.1):
@@ -544,7 +544,7 @@ EXTERNAL SOURCES:
544544
SPEC CHECKSUMS:
545545
boost: a7c83b31436843459a1961bfd74b96033dc77234
546546
CocoaAsyncSocket: 065fd1e645c7abab64f7a6a2007a48038fdc6a99
547-
DoubleConversion: 5189b271737e1565bdce30deb4a08d647e3f5f54
547+
DoubleConversion: 831926d9b8bf8166fd87886c4abab286c2422662
548548
FBLazyVector: affa4ba1bfdaac110a789192f4d452b053a86624
549549
FBReactNativeSpec: fe8b5f1429cfe83a8d72dc8ed61dc7704cac8745
550550
Flipper: 26fc4b7382499f1281eb8cb921e5c3ad6de91fe0
@@ -557,12 +557,12 @@ SPEC CHECKSUMS:
557557
Flipper-RSocket: d9d9ade67cbecf6ac10730304bf5607266dd2541
558558
FlipperKit: cbdee19bdd4e7f05472a66ce290f1b729ba3cb86
559559
fmt: ff9d55029c625d3757ed641535fd4a75fedc7ce9
560-
glog: 04b94705f318337d7ead9e6d17c019bd9b1f6b1b
560+
glog: 476ee3e89abb49e07f822b48323c51c57124b572
561561
hermes-engine: 7fe5fc6ef707b7fdcb161b63898ec500e285653d
562562
libevent: 4049cae6c81cdb3654a443be001fb9bdceff7913
563563
NWWebSocket: 21f0c73639815da3272862c912275b26102aa80c
564564
OpenSSL-Universal: ebc357f1e6bc71fa463ccb2fe676756aff50e88c
565-
pusher-websocket-react-native: e3b93548fc89a85e79d68ace4c6e8a7eca80315f
565+
pusher-websocket-react-native: f39fffc44df8914ca5c9382a0acb446130d9946e
566566
PusherSwift: 70a805a950ab49381e164c54ac19c3b9c1d288e3
567567
RCT-Folly: 0080d0a6ebf2577475bda044aa59e2ca1f909cda
568568
RCTRequired: 21229f84411088e5d8538f21212de49e46cc83e2

example/src/App.tsx

Lines changed: 28 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import * as React from 'react';
2+
23
import {
34
StyleSheet,
45
View,
@@ -11,8 +12,13 @@ import {
1112
FlatList,
1213
} from 'react-native';
1314
import AsyncStorage from '@react-native-async-storage/async-storage';
14-
import CryptoES from 'crypto-es';
15-
import { Pusher, PusherMember, PusherChannel, PusherEvent } from '../../src'; // This links the example app to the current SDK implementation
15+
import {
16+
Pusher,
17+
PusherMember,
18+
PusherChannel,
19+
PusherEvent,
20+
PusherAuthorizerResult,
21+
} from '../../src'; // This links the example app to the current SDK implementation
1622

1723
export default function App() {
1824
let logLines: string[] = [];
@@ -149,17 +155,26 @@ export default function App() {
149155
};
150156

151157
// See https://pusher.com/docs/channels/library_auth_reference/auth-signatures/ for the format of this object.
152-
const onAuthorizer = (channelName: string, socketId: string) => {
153-
const user = JSON.stringify({ user_id: 12345 });
154-
const stringToSign = socketId + ':' + channelName + ':' + user;
155-
const pusherKey = '<YOUR PUSHER KEY>';
156-
const pusherSecret = '<YOUR PUSHER SECRET>';
157-
const signature = CryptoES.HmacSHA256(stringToSign, pusherSecret);
158-
return {
159-
auth: pusherKey + ':' + signature,
160-
channel_data: user,
161-
shared_secret: 'foobar',
162-
};
158+
const onAuthorizer = async (channelName: string, socketId: string) => {
159+
log(
160+
`calling onAuthorizer. channelName=${channelName}, socketId=${socketId}`
161+
);
162+
163+
const response = await fetch('some_url', {
164+
method: 'POST',
165+
headers: {
166+
'Content-Type': 'application/json',
167+
},
168+
body: JSON.stringify({
169+
socket_id: socketId,
170+
channel_name: channelName,
171+
}),
172+
});
173+
174+
const body = (await response.json()) as PusherAuthorizerResult;
175+
176+
log(`response: ${JSON.stringify(body)}`);
177+
return body;
163178
};
164179

165180
const trigger = async () => {

ios/PusherWebsocketReactNative.swift

Lines changed: 26 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@ import Foundation
55
@objcMembers class PusherWebsocketReactNative: RCTEventEmitter, PusherDelegate, Authorizer {
66
private var pusher: Pusher!
77

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

1111
private let subscriptionErrorType = "SubscriptionError"
1212
private let authErrorType = "AuthError"
@@ -85,6 +85,11 @@ import Foundation
8585
if args["pongTimeout"] is Int {
8686
pusher.connection.pongResponseTimeoutInterval = args["pongTimeout"] as! TimeInterval / 1000.0
8787
}
88+
89+
if let authorizerTimeoutInSeconds = args["authorizerTimeoutInSeconds"] as? Int {
90+
self.authorizerCompletionHandlerTimeout = authorizerTimeoutInSeconds
91+
}
92+
8893
pusher.connection.delegate = self
8994
pusher.bind(eventCallback: onEvent)
9095
resolve(nil)
@@ -101,17 +106,29 @@ import Foundation
101106
])
102107

103108
let key = channelName + socketID
104-
authorizerMutex[key] = DispatchSemaphore(value: 0)
105-
authorizerMutex[key]!.wait()
106-
let authParams = authorizerResult.removeValue(forKey: key)!
107-
completionHandler(PusherAuth(auth: authParams["auth"]!, channelData: authParams["channel_data"], sharedSecret: authParams["shared_secret"]))
109+
let authCallback = { (authParams:[String:String]) in
110+
if let authParam = authParams["auth"] {
111+
completionHandler(PusherAuth(auth: authParam, channelData: authParams["channel_data"], sharedSecret: authParams["shared_secret"]))
112+
} else {
113+
completionHandler(PusherAuth(auth: "<missing_auth_param>:error", channelData: authParams["channel_data"], sharedSecret: authParams["shared_secret"]))
114+
}
115+
}
116+
authorizerCompletionHandlers[key] = authCallback
117+
118+
// the JS thread might not call onAuthorizer – we need to cleanup the completion handler after timeout
119+
let timeout = DispatchTimeInterval.seconds(self.authorizerCompletionHandlerTimeout)
120+
DispatchQueue.main.asyncAfter(deadline: .now() + timeout) {
121+
if let storedAuthHandler = self.authorizerCompletionHandlers.removeValue(forKey: key) {
122+
storedAuthHandler(["auth": "<authorizer_timeout>:error"])
123+
}
124+
}
108125
}
109126

110127
public func onAuthorizer(_ channelName: String, socketID: String, data:[String:String], resolve:RCTPromiseResolveBlock,reject:RCTPromiseRejectBlock) {
111128
let key = channelName + socketID
112-
authorizerResult[key] = data
113-
authorizerMutex[key]!.signal()
114-
authorizerMutex.removeValue(forKey: key)
129+
if let storedAuthHandler = authorizerCompletionHandlers.removeValue(forKey: key) {
130+
storedAuthHandler(data)
131+
}
115132
}
116133

117134

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@pusher/pusher-websocket-react-native",
3-
"version": "1.1.1",
3+
"version": "1.2.0",
44
"description": "Pusher Channels Client for React Native",
55
"main": "lib/commonjs/index",
66
"module": "lib/module/index",

src/index.tsx

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,15 @@ const PusherWebsocketReactNative = NativeModules.PusherWebsocketReactNative
1717
}
1818
);
1919

20+
export interface PusherAuthorizerResult {
21+
/** required for private channels */
22+
auth?: string;
23+
/** required for encrypted channels */
24+
shared_secret?: string;
25+
/** required for presence channels, should be stringified JSON */
26+
channel_data?: string;
27+
}
28+
2029
export class PusherEvent {
2130
channelName: string;
2231
eventName: string;
@@ -124,12 +133,16 @@ export class Pusher {
124133
pongTimeout?: Number;
125134
maxReconnectionAttempts?: Number;
126135
maxReconnectGapInSeconds?: Number;
136+
authorizerTimeoutInSeconds?: Number;
127137
proxy?: string;
128138
onConnectionStateChange?: (
129139
currentState: string,
130140
previousState: string
131141
) => void;
132-
onAuthorizer?: (channelName: string, socketId: string) => any;
142+
onAuthorizer?: (
143+
channelName: string,
144+
socketId: string
145+
) => Promise<PusherAuthorizerResult>;
133146
onError?: (message: string, code: Number, e: any) => void;
134147
onEvent?: (event: PusherEvent) => void;
135148
onSubscriptionSucceeded?: (channelName: string, data: any) => void;
@@ -245,6 +258,7 @@ export class Pusher {
245258
pongTimeout: args.pongTimeout,
246259
maxReconnectionAttempts: args.maxReconnectionAttempts,
247260
maxReconnectGapInSeconds: args.maxReconnectGapInSeconds,
261+
authorizerTimeoutInSeconds: args.authorizerTimeoutInSeconds,
248262
authorizer: args.onAuthorizer ? true : false,
249263
proxy: args.proxy,
250264
});

0 commit comments

Comments
 (0)