Comparing version 7.3.0 to 7.4.0
# Changelog | ||
## 7.4.0 | ||
* [FIXED] Allow presence channel authorization to depend on user authentication | ||
## 7.3.0 | ||
@@ -4,0 +8,0 @@ |
{ | ||
"name": "pusher-js", | ||
"version": "7.3.0", | ||
"version": "7.4.0", | ||
"description": "Pusher Channels JavaScript library for browsers, React Native, NodeJS and web workers", | ||
@@ -41,3 +41,3 @@ "main": "dist/node/pusher.js", | ||
"isomorphic-fetch": "^3.0.0", | ||
"jasmine": "^3.8.0", | ||
"jasmine": "^3.99.0", | ||
"jasmine-spec-reporter": "^7.0.0", | ||
@@ -58,3 +58,3 @@ "karma": "^6.4.0", | ||
"ts-loader": "^6.0.4", | ||
"typescript": "^3.4.5", | ||
"typescript": "^3.9.0", | ||
"uglify-js": "^2.6.2", | ||
@@ -68,4 +68,5 @@ "webpack": "^4.46.0", | ||
"dependencies": { | ||
"tweetnacl": "^1.0.3" | ||
"tweetnacl": "^1.0.3", | ||
"@types/node": "^14.14.31" | ||
} | ||
} |
@@ -5,4 +5,3 @@ # Pusher Channels Javascript Client | ||
This Pusher Channels client library supports web browsers, web workers, Node.js | ||
and React Native. | ||
This Pusher Channels client library supports web browsers, web workers and Node.js | ||
@@ -149,26 +148,8 @@ If you're looking for the Pusher Channels server library for Node.js, use | ||
**Warning it's now necessary to install | ||
[@react-native-community/netinfo](https://github.com/react-native-community/react-native-netinfo) | ||
in order to use pusher-js with react-native.** pusher-js depends on NetInfo. | ||
NetInfo. NetInfo was included within react-native core until v0.60, when it was | ||
moved to the | ||
[@react-native-community/netinfo](https://github.com/react-native-community/react-native-netinfo) | ||
library. Please follow the [install | ||
instructions](https://github.com/react-native-community/react-native-netinfo#getting-started) | ||
for the | ||
[@react-native-community/netinfo](https://github.com/react-native-community/react-native-netinfo) | ||
library before trying to use pusher-js in your react-native project. | ||
> **⚠️ Important notice** | ||
> | ||
> React Native support has been **deprecated** and soon will be removed from this repository. | ||
> | ||
> Please, use our official [React Native SDK](https://github.com/pusher/pusher-websocket-react-native) instead. | ||
Use a package manager like Yarn or NPM to install `pusher-js` and then import | ||
it as follows: | ||
```javascript | ||
import Pusher from 'pusher-js/react-native'; | ||
``` | ||
Notes: | ||
* The fallbacks available for this runtime are HTTP streaming and polling. | ||
* This build uses React Native's NetInfo API to detect changes on connectivity state. It will use this to automatically reconnect. | ||
### Web Workers | ||
@@ -259,3 +240,3 @@ (`pusher-js`'s Web Workers implementation is currently not compatible with Internet Explorer) | ||
For more information see [authenticating users](https://pusher.com/docs/channels/server_api/authenticating-users/). | ||
For more information see [authenticating users](https://pusher.com/docs/channels/server_api/authenticating-users/). | ||
@@ -280,3 +261,3 @@ | ||
For more information see [authorizing users](https://pusher.com/docs/channels/server_api/authorizing-users). | ||
For more information see [authorizing users](https://pusher.com/docs/channels/server_api/authorizing-users). | ||
@@ -650,3 +631,3 @@ | ||
New to pusher-js 3.1 is the ability for the library to produce builds for different runtimes: classic web, React Native, NodeJS and | ||
New to pusher-js 3.1 is the ability for the library to produce builds for different runtimes: classic web, NodeJS and | ||
Web Workers. | ||
@@ -690,3 +671,2 @@ | ||
```bash | ||
make react-native # for the React Native build | ||
make node # for the NodeJS build | ||
@@ -705,3 +685,3 @@ make worker # for the worker build | ||
There are 3 different testing environments: one for web, one for NodeJS and one for workers. We may consider adding another one for React Native in the future. | ||
There are 3 different testing environments: one for web, one for NodeJS and one for workers. | ||
@@ -708,0 +688,0 @@ The web and worker tests use [Karma](https://github.com/karma-runner/karma) to execute specs in real browsers. The NodeJS tests use [jasmine-node](https://github.com/mhevery/jasmine-node). |
@@ -7,2 +7,4 @@ var PresenceChannel = require('core/channels/presence_channel').default; | ||
var Mocks = require("mocks"); | ||
var flatPromise = require("core/utils/flat_promise").default; | ||
var { setTimeout } = require("../../../helpers/timers/promises").default; | ||
@@ -51,3 +53,3 @@ describe("PresenceChannel", function() { | ||
describe("#authorize", function() { | ||
it("should call channelAuthorizer", function() { | ||
@@ -61,3 +63,3 @@ const callback = function(){} | ||
it("should call the callback if an authorizaiton error is encountered", function() { | ||
it("should call the callback if an authorization error is encountered", function() { | ||
const callback = jasmine.createSpy("callback") | ||
@@ -69,3 +71,3 @@ channel.authorize("1.23", callback); | ||
const presenceChannelCallback = channelAuthorizer.calls.mostRecent().args[1]; | ||
presenceChannelCallback("error", {}) | ||
@@ -75,14 +77,81 @@ expect(callback).toHaveBeenCalledWith("error", {}) | ||
it("should call the callback with error if auth data doesn't have channel_data", function() { | ||
const callback = jasmine.createSpy("callback") | ||
channel.authorize("1.23", callback); | ||
expect(channelAuthorizer.calls.count()).toEqual(1); | ||
expect(channelAuthorizer).toHaveBeenCalledWith( | ||
{ socketId: "1.23", channelName: "presence-test" }, jasmine.any(Function)); | ||
const presenceChannelCallback = channelAuthorizer.calls.mostRecent().args[1]; | ||
presenceChannelCallback(null, { | ||
foo: 'bar' | ||
}) | ||
expect(callback).toHaveBeenCalledWith("Invalid auth response") | ||
describe("when channel_data isn't present", function() { | ||
let callback; | ||
let presenceChannelCallback; | ||
const authWithoutChannelData = {foo: 'bar'} | ||
beforeEach(function() { | ||
pusher.user = jasmine.createSpy("user"); | ||
callback = jasmine.createSpy("callback") | ||
channel.authorize("1.23", callback); | ||
expect(channelAuthorizer.calls.count()).toEqual(1); | ||
expect(channelAuthorizer).toHaveBeenCalledWith( | ||
{ socketId: "1.23", channelName: "presence-test" }, jasmine.any(Function)); | ||
presenceChannelCallback = channelAuthorizer.calls.mostRecent().args[1]; | ||
}); | ||
it("should call the callback with an error if no signin is in progress", async function() { | ||
pusher.user.signinDonePromise = null; | ||
presenceChannelCallback(null, authWithoutChannelData); | ||
await setTimeout(10); | ||
expect(callback).toHaveBeenCalledWith("Invalid auth response"); | ||
}); | ||
it("should wait for the in-progress sign in and call the callback with an error if signin failed", async function() { | ||
const {promise, resolve} = flatPromise(); | ||
pusher.user.signinDonePromise = promise | ||
// signin is in progress | ||
presenceChannelCallback(null, authWithoutChannelData) | ||
await setTimeout(10); | ||
expect(callback).not.toHaveBeenCalled() | ||
// Signin failed | ||
pusher.user.user_data = null | ||
resolve() | ||
await setTimeout(10); | ||
expect(callback).toHaveBeenCalledWith("Invalid auth response") | ||
}); | ||
it("should wait for the in-progress sign in and call the callback if signin succeeded", async function() { | ||
const {promise, resolve} = flatPromise(); | ||
pusher.user.signinDonePromise = promise | ||
// signin is in progress | ||
presenceChannelCallback(null, authWithoutChannelData) | ||
await setTimeout(10); | ||
expect(callback).not.toHaveBeenCalled() | ||
// Signin succeeded | ||
pusher.user.user_data = {id: '123'} | ||
resolve() | ||
await setTimeout(10); | ||
expect(callback).toHaveBeenCalledWith(null, authWithoutChannelData) | ||
}); | ||
it("should call the callback if the user is already signed in", async function() { | ||
const {promise, resolve} = flatPromise(); | ||
pusher.user.signinDonePromise = promise | ||
resolve() | ||
pusher.user.user_data = {id: '123'} | ||
// signin is in progress | ||
presenceChannelCallback(null, authWithoutChannelData) | ||
await setTimeout(10); | ||
expect(callback).toHaveBeenCalledWith(null, authWithoutChannelData) | ||
}); | ||
it("should call the callback with an error if the user signin already failed", async function() { | ||
const {promise, resolve} = flatPromise(); | ||
pusher.user.signinDonePromise = promise | ||
resolve() | ||
pusher.user.user_data = null | ||
// signin is in progress | ||
presenceChannelCallback(null, authWithoutChannelData) | ||
await setTimeout(10); | ||
expect(callback).toHaveBeenCalledWith("Invalid auth response") | ||
}); | ||
}); | ||
@@ -97,3 +166,3 @@ | ||
const presenceChannelCallback = channelAuthorizer.calls.mostRecent().args[1]; | ||
const authdata = { | ||
@@ -100,0 +169,0 @@ channel_data: "{\"user_id\":\"123\"}", |
@@ -85,3 +85,3 @@ var TestEnv = require("testenv"); | ||
pusher.connection.state = "connected"; | ||
pusher.connection.emit('connected'); | ||
pusher.connection.emit('state_change', {previous:'connecting', current:'connected'}); | ||
@@ -120,7 +120,7 @@ expect(pusher.config.userAuthenticator).toHaveBeenCalledWith( | ||
pusher.connection.state == "disconnected"; | ||
pusher.connection.emit("disconnected"); | ||
pusher.connection.emit('state_change', {previous:'connected', current:'disconnected'}); | ||
pusher.connection.state == "connecting"; | ||
pusher.connection.emit("connecting"); | ||
pusher.connection.emit('state_change', {previous:'disconnected', current:'connecting'}); | ||
pusher.connection.state == "connected"; | ||
pusher.connection.emit("connected"); | ||
pusher.connection.emit('state_change', {previous:'connecting', current:'connected'}); | ||
@@ -139,3 +139,3 @@ expect(pusher.config.userAuthenticator).toHaveBeenCalledWith( | ||
pusher.connection.state = "connected"; | ||
pusher.connection.emit('connected'); | ||
pusher.connection.emit('state_change', {previous:'connecting', current:'connected'}); | ||
expect(pusher.config.userAuthenticator).not.toHaveBeenCalled(); | ||
@@ -178,2 +178,3 @@ }) | ||
it('should process pusher:signin_success', async function () { | ||
pusher.user._signinDoneResolve = jasmine.createSpy('signinDoneResolve'); | ||
transport.emit('message', { | ||
@@ -190,2 +191,3 @@ data: JSON.stringify({ | ||
expect(pusher.user.serverToUserChannel.subscriptionPending).toBe(true); | ||
expect(pusher.user._signinDoneResolve).toHaveBeenCalled(); | ||
}); | ||
@@ -228,2 +230,3 @@ | ||
// Sign in successfully | ||
pusher.user._signinDoneResolve = jasmine.createSpy('signinDoneResolve'); | ||
transport.emit('message', { | ||
@@ -251,2 +254,3 @@ data: JSON.stringify({ | ||
); | ||
expect(pusher.user._signinDoneResolve).toHaveBeenCalled(); | ||
@@ -269,2 +273,3 @@ // Send events on channel | ||
// Sign in successfully | ||
pusher.user._signinDoneResolve = jasmine.createSpy('signinDoneResolve'); | ||
transport.emit('message', { | ||
@@ -292,2 +297,4 @@ data: JSON.stringify({ | ||
); | ||
expect(pusher.user._signinDoneResolve).toHaveBeenCalled(); | ||
expect(pusher.user.user_data).toEqual({ id: '1', name: 'test' }); | ||
@@ -297,3 +304,3 @@ expect(pusher.user.serverToUserChannel.subscribed).toBe(true); | ||
// Disconnect | ||
pusher.connection.emit('disconnected'); | ||
pusher.connection.emit('state_change', {previous:'connected', current:'disconnected'}); | ||
@@ -300,0 +307,0 @@ expect(pusher.user.user_data).toEqual(null); |
@@ -23,3 +23,3 @@ import PrivateChannel from './private_channel'; | ||
/** Authenticates the connection as a member of the channel. | ||
/** Authorizes the connection as a member of the channel. | ||
* | ||
@@ -30,16 +30,25 @@ * @param {String} socketId | ||
authorize(socketId: string, callback: Function) { | ||
super.authorize(socketId, (error, authData) => { | ||
super.authorize(socketId, async (error, authData) => { | ||
if (!error) { | ||
authData = authData as ChannelAuthorizationData; | ||
if (authData.channel_data === undefined) { | ||
let suffix = UrlStore.buildLogSuffix('authenticationEndpoint'); | ||
Logger.error( | ||
`Invalid auth response for channel '${this.name}',` + | ||
`expected 'channel_data' field. ${suffix}` | ||
); | ||
callback('Invalid auth response'); | ||
return; | ||
if (authData.channel_data != null) { | ||
var channelData = JSON.parse(authData.channel_data); | ||
this.members.setMyID(channelData.user_id); | ||
} else { | ||
await this.pusher.user.signinDonePromise; | ||
if (this.pusher.user.user_data != null) { | ||
// If the user is signed in, get the id of the authenticated user | ||
// and allow the presence authorization to continue. | ||
this.members.setMyID(this.pusher.user.user_data.id); | ||
} else { | ||
let suffix = UrlStore.buildLogSuffix('authorizationEndpoint'); | ||
Logger.error( | ||
`Invalid auth response for channel '${this.name}', ` + | ||
`expected 'channel_data' field. ${suffix}, ` + | ||
`or the user should be signed in.` | ||
); | ||
callback('Invalid auth response'); | ||
return; | ||
} | ||
} | ||
var channelData = JSON.parse(authData.channel_data); | ||
this.members.setMyID(channelData.user_id); | ||
} | ||
@@ -46,0 +55,0 @@ callback(error, authData); |
@@ -207,3 +207,3 @@ import URLLocation from './url_location'; | ||
function randomNumber(max: number): number { | ||
return Math.floor(Math.random() * max); | ||
return Runtime.randomInt(max); | ||
} | ||
@@ -213,5 +213,7 @@ | ||
var result = []; | ||
for (var i = 0; i < length; i++) { | ||
result.push(randomNumber(32).toString(32)); | ||
} | ||
return result.join(''); | ||
@@ -218,0 +220,0 @@ } |
@@ -86,3 +86,3 @@ import AbstractRuntime from '../runtimes/interface'; | ||
this.global_emitter = new EventsDispatcher(); | ||
this.sessionID = Math.floor(Math.random() * 1000000000); | ||
this.sessionID = Runtime.randomInt(1000000000); | ||
@@ -89,0 +89,0 @@ this.timeline = new Timeline(this.key, this.sessionID, { |
@@ -9,2 +9,3 @@ import Pusher from './pusher'; | ||
import EventsDispatcher from './events/dispatcher'; | ||
import flatPromise from './utils/flat_promise'; | ||
@@ -16,2 +17,4 @@ export default class UserFacade extends EventsDispatcher { | ||
serverToUserChannel: Channel = null; | ||
signinDonePromise: Promise<any> = null; | ||
private _signinDoneResolve: Function = null; | ||
@@ -23,11 +26,11 @@ public constructor(pusher: Pusher) { | ||
this.pusher = pusher; | ||
this.pusher.connection.bind('connected', () => { | ||
this._signin(); | ||
this.pusher.connection.bind('state_change', ({ previous, current }) => { | ||
if (previous !== 'connected' && current === 'connected') { | ||
this._signin(); | ||
} | ||
if (previous === 'connected' && current !== 'connected') { | ||
this._cleanup(); | ||
this._newSigninPromiseIfNeeded(); | ||
} | ||
}); | ||
this.pusher.connection.bind('connecting', () => { | ||
this._disconnect(); | ||
}); | ||
this.pusher.connection.bind('disconnected', () => { | ||
this._disconnect(); | ||
}); | ||
this.pusher.connection.bind('message', event => { | ||
@@ -61,2 +64,4 @@ var eventName = event.event; | ||
this._newSigninPromiseIfNeeded(); | ||
if (this.pusher.connection.state !== 'connected') { | ||
@@ -67,19 +72,2 @@ // Signin will be attempted when the connection is connected | ||
const onAuthorize: UserAuthenticationCallback = ( | ||
err, | ||
authData: UserAuthenticationData | ||
) => { | ||
if (err) { | ||
Logger.warn(`Error during signin: ${err}`); | ||
return; | ||
} | ||
this.pusher.send_event('pusher:signin', { | ||
auth: authData.auth, | ||
user_data: authData.user_data | ||
}); | ||
// Later when we get pusher:singin_success event, the user will be marked as signed in | ||
}; | ||
this.pusher.config.userAuthenticator( | ||
@@ -89,6 +77,24 @@ { | ||
}, | ||
onAuthorize | ||
this._onAuthorize | ||
); | ||
} | ||
private _onAuthorize: UserAuthenticationCallback = ( | ||
err, | ||
authData: UserAuthenticationData | ||
) => { | ||
if (err) { | ||
Logger.warn(`Error during signin: ${err}`); | ||
this._cleanup(); | ||
return; | ||
} | ||
this.pusher.send_event('pusher:signin', { | ||
auth: authData.auth, | ||
user_data: authData.user_data | ||
}); | ||
// Later when we get pusher:singin_success event, the user will be marked as signed in | ||
}; | ||
private _onSigninSuccess(data: any) { | ||
@@ -99,2 +105,3 @@ try { | ||
Logger.error(`Failed parsing user data after signin: ${data.user_data}`); | ||
this._cleanup(); | ||
return; | ||
@@ -107,5 +114,8 @@ } | ||
); | ||
this._cleanup(); | ||
return; | ||
} | ||
// Signin succeeded | ||
this._signinDoneResolve(); | ||
this._subscribeChannels(); | ||
@@ -143,3 +153,3 @@ } | ||
private _disconnect() { | ||
private _cleanup() { | ||
this.user_data = null; | ||
@@ -151,3 +161,31 @@ if (this.serverToUserChannel) { | ||
} | ||
if (this.signin_requested) { | ||
// If signin is in progress and cleanup is called, | ||
// Mark the current signin process as done. | ||
this._signinDoneResolve(); | ||
} | ||
} | ||
private _newSigninPromiseIfNeeded() { | ||
if (!this.signin_requested) { | ||
return; | ||
} | ||
// If there is a promise and it is not resolved, return without creating a new one. | ||
if (this.signinDonePromise && !(this.signinDonePromise as any).done) { | ||
return; | ||
} | ||
// This promise is never rejected. | ||
// It gets resolved when the signin process is done whether it failed or succeeded | ||
const { promise, resolve, reject: _ } = flatPromise(); | ||
(promise as any).done = false; | ||
const setDone = () => { | ||
(promise as any).done = true; | ||
}; | ||
promise.then(setDone).catch(setDone); | ||
this.signinDonePromise = promise; | ||
this._signinDoneResolve = resolve; | ||
} | ||
} |
@@ -49,2 +49,3 @@ import { AuthTransports } from '../core/auth/auth_transports'; | ||
createSocketRequest(method: string, url: string): HTTPRequest; | ||
randomInt(max: number): number; | ||
@@ -51,0 +52,0 @@ // these methods/types are only implemented in the web Runtime, so they're |
@@ -9,2 +9,3 @@ import Isomorphic from 'isomorphic/runtime'; | ||
import xhrTimeline from 'isomorphic/timeline/xhr_timeline'; | ||
import { randomInt } from 'crypto'; | ||
@@ -61,2 +62,6 @@ // Very verbose but until unavoidable until | ||
return Network; | ||
}, | ||
randomInt(max: number): number { | ||
return randomInt(max); | ||
} | ||
@@ -63,0 +68,0 @@ }; |
@@ -7,2 +7,3 @@ import Isomorphic from 'isomorphic/runtime'; | ||
import xhrTimeline from 'isomorphic/timeline/xhr_timeline'; | ||
import { randomInt } from 'crypto'; | ||
@@ -59,2 +60,6 @@ // Very verbose but until unavoidable until | ||
return Network; | ||
}, | ||
randomInt(max: number): number { | ||
return randomInt(max); | ||
} | ||
@@ -61,0 +66,0 @@ }; |
@@ -157,2 +157,16 @@ import Browser from './browser'; | ||
} | ||
}, | ||
randomInt(max: number): number { | ||
/** | ||
* Return values in the range of [0, 1[ | ||
*/ | ||
const random = function() { | ||
const crypto = window.crypto || window['msCrypto']; | ||
const random = crypto.getRandomValues(new Uint32Array(1))[0]; | ||
return random / 2 ** 32; | ||
}; | ||
return Math.floor(random() * max); | ||
} | ||
@@ -159,0 +173,0 @@ }; |
@@ -58,2 +58,16 @@ import Isomorphic from 'isomorphic/runtime'; | ||
return Network; | ||
}, | ||
randomInt(max: number): number { | ||
/** | ||
* Return values in the range of [0, 1[ | ||
*/ | ||
const random = function() { | ||
const crypto = window.crypto || window['msCrypto']; | ||
const random = crypto.getRandomValues(new Uint32Array(1))[0]; | ||
return random / 2 ** 32; | ||
}; | ||
return Math.floor(random() * max); | ||
} | ||
@@ -60,0 +74,0 @@ }; |
@@ -13,4 +13,7 @@ import Strategy from './strategy'; | ||
abort: () => void; | ||
forceMinPriority: () => void; | ||
} | { | ||
abort: () => void; | ||
forceMinPriority: (p: any) => void; | ||
}; | ||
} |
@@ -9,8 +9,12 @@ import Pusher from './pusher'; | ||
serverToUserChannel: Channel; | ||
signinDonePromise: Promise<any>; | ||
private _signinDoneResolve; | ||
constructor(pusher: Pusher); | ||
signin(): void; | ||
private _signin; | ||
private _onAuthorize; | ||
private _onSigninSuccess; | ||
private _subscribeChannels; | ||
private _disconnect; | ||
private _cleanup; | ||
private _newSigninPromiseIfNeeded; | ||
} |
@@ -36,2 +36,3 @@ import { AuthTransports } from '../core/auth/auth_transports'; | ||
createSocketRequest(method: string, url: string): HTTPRequest; | ||
randomInt(max: number): number; | ||
getDocument?(): Document; | ||
@@ -38,0 +39,0 @@ createScriptRequest?(url: string): any; |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
6100144
392
60634
2
700
+ Added@types/node@^14.14.31
+ Added@types/node@14.18.63(transitive)