forked from laravel/echo
-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #42 from ably-forks/fix/broadcast-to-others
[ECO-4977] Fix broadcast to others
- Loading branch information
Showing
9 changed files
with
295 additions
and
40 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -39,7 +39,7 @@ describe('AblyUserLogin', () => { | |
}); | ||
}); | ||
|
||
test('user logs in without previous (guest) channels', async () => { | ||
test('user logs in without previous (public) channels', async () => { | ||
let connectionStates : Array<any>= [] | ||
// Initial clientId is null | ||
expect(mockAuthServer.clientId).toBeNull(); | ||
|
@@ -70,7 +70,7 @@ describe('AblyUserLogin', () => { | |
expect(echo.connector.ablyAuth.existingToken().clientId).toBe('[email protected]'); | ||
}); | ||
|
||
test('user logs in with previous (guest) channels', async () => { | ||
test('user logs in with previous (public) channels', async () => { | ||
let connectionStates : Array<any>= [] | ||
let publicChannelStates : Array<any>= [] | ||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,167 @@ | ||
import { setup, tearDown } from './setup/sandbox'; | ||
import Echo from '../../src/echo'; | ||
import { MockAuthServer } from './setup/mock-auth-server'; | ||
import { AblyChannel, AblyPrivateChannel } from '../../src/channel'; | ||
import * as Ably from 'ably'; | ||
import waitForExpect from 'wait-for-expect'; | ||
import { fromBase64UrlEncoded } from '../../src/channel/ably/utils'; | ||
|
||
jest.setTimeout(30000); | ||
describe('AblyUserRestPublishing', () => { | ||
let testApp: any; | ||
let mockAuthServer: MockAuthServer; | ||
let echoInstances: Array<Echo>; | ||
|
||
beforeAll(async () => { | ||
global.Ably = Ably; | ||
testApp = await setup(); | ||
mockAuthServer = new MockAuthServer(testApp.keys[0].keyStr); | ||
}); | ||
|
||
afterAll(async () => { | ||
return await tearDown(testApp); | ||
}); | ||
|
||
beforeEach(() => { | ||
echoInstances = []; | ||
}) | ||
|
||
afterEach((done) => { | ||
let promises: Array<Promise<boolean>> = [] | ||
for (const echo of echoInstances) { | ||
echo.disconnect(); | ||
const promise = new Promise<boolean>(res => { | ||
echo.connector.ably.connection.once('closed', () => { | ||
res(true); | ||
}); | ||
}) | ||
promises.push(promise); | ||
} | ||
Promise.all(promises).then(_ => { | ||
done(); | ||
}) | ||
}); | ||
|
||
async function getGuestUserChannel(channelName: string) { | ||
mockAuthServer.clientId = null; | ||
const guestUser = new Echo({ | ||
broadcaster: 'ably', | ||
useTls: true, | ||
environment: 'sandbox', | ||
requestTokenFn: mockAuthServer.getSignedToken | ||
}); | ||
echoInstances.push(guestUser); | ||
const publicChannel = guestUser.channel(channelName) as AblyChannel; | ||
await new Promise((resolve) => publicChannel.subscribed(resolve)); | ||
expect(guestUser.connector.ably.auth.clientId).toBeFalsy(); | ||
expect(guestUser.connector.ablyAuth.existingToken().clientId).toBeNull(); | ||
return publicChannel; | ||
} | ||
|
||
async function getLoggedInUserChannel(channelName: string) { | ||
mockAuthServer.clientId = '[email protected]'; | ||
const loggedInUser = new Echo({ | ||
broadcaster: 'ably', | ||
useTls: true, | ||
environment: 'sandbox', | ||
requestTokenFn: mockAuthServer.getSignedToken | ||
}); | ||
echoInstances.push(loggedInUser); | ||
const privateChannel = loggedInUser.private(channelName) as AblyPrivateChannel; | ||
await new Promise((resolve) => privateChannel.subscribed(resolve)); | ||
expect(loggedInUser.connector.ably.auth.clientId).toBe("[email protected]"); | ||
expect(loggedInUser.connector.ablyAuth.existingToken().clientId).toBe("[email protected]") | ||
mockAuthServer.clientId = null; | ||
return privateChannel; | ||
} | ||
|
||
test('Guest user return socketId as base64 encoded connectionkey and null clientId', async () => { | ||
await getGuestUserChannel("dummyChannel"); | ||
const guestUser = echoInstances[0]; | ||
const socketIdObj = JSON.parse(fromBase64UrlEncoded(guestUser.socketId())); | ||
|
||
const expectedConnectionKey = guestUser.connector.ably.connection.key; | ||
|
||
expect(socketIdObj.connectionKey).toBe(expectedConnectionKey); | ||
expect(socketIdObj.connectionKey).toBeTruthy(); | ||
|
||
expect(socketIdObj.clientId).toBeNull(); | ||
}); | ||
|
||
test('Guest user publishes message via rest API', async () => { | ||
let messagesReceived: Array<string> = [] | ||
let channelName = "testChannel"; | ||
|
||
const publicChannel1 = await getGuestUserChannel(channelName); | ||
publicChannel1.listenToAll((eventName, data) => { | ||
messagesReceived.push(eventName); | ||
}); | ||
|
||
const publicChannel2 = await getGuestUserChannel(channelName); | ||
publicChannel2.listenToAll((eventName, data) => { | ||
messagesReceived.push(eventName); | ||
}) | ||
|
||
// Publish message to all clients | ||
await mockAuthServer.broadcast(`public:${channelName}`, "testEvent", "mydata") | ||
await waitForExpect(() => { | ||
expect(messagesReceived.length).toBe(2); | ||
expect(messagesReceived.filter(m => m == ".testEvent").length).toBe(2) | ||
}); | ||
|
||
// Publish message to other client | ||
messagesReceived = [] | ||
const firstClientSocketId = echoInstances[0].socketId(); | ||
await mockAuthServer.broadcastToOthers(firstClientSocketId, | ||
{ channelName: `public:${channelName}`, eventName: "toOthers", payload: "data" }) | ||
await waitForExpect(() => { | ||
expect(messagesReceived.length).toBe(1); | ||
expect(messagesReceived.filter(m => m == ".toOthers").length).toBe(1); | ||
}); | ||
}); | ||
|
||
test('Logged in user return socketId as base64 encoded connectionkey and clientId', async () => { | ||
await getLoggedInUserChannel("dummyChannel"); | ||
const loggedInUser = echoInstances[0]; | ||
const socketIdObj = JSON.parse(fromBase64UrlEncoded(loggedInUser.socketId())); | ||
|
||
const expectedConnectionKey = loggedInUser.connector.ably.connection.key; | ||
|
||
expect(socketIdObj.connectionKey).toBe(expectedConnectionKey); | ||
expect(socketIdObj.connectionKey).toBeTruthy(); | ||
|
||
expect(socketIdObj.clientId).toBe("[email protected]"); | ||
}); | ||
|
||
test('Logged in user publishes message via rest API', async () => { | ||
let messagesReceived: Array<string> = [] | ||
let channelName = "testChannel"; | ||
|
||
const privateChannel1 = await getLoggedInUserChannel(channelName); | ||
privateChannel1.listenToAll((eventName, data) => { | ||
messagesReceived.push(eventName); | ||
}); | ||
|
||
const privateChannel2 = await getLoggedInUserChannel(channelName); | ||
privateChannel2.listenToAll((eventName, data) => { | ||
messagesReceived.push(eventName); | ||
}) | ||
|
||
// Publish message to all clients | ||
await mockAuthServer.broadcast(`private:${channelName}`, "testEvent", "mydata") | ||
await waitForExpect(() => { | ||
expect(messagesReceived.length).toBe(2); | ||
expect(messagesReceived.filter(m => m == ".testEvent").length).toBe(2); | ||
}); | ||
|
||
// Publish message to other client | ||
messagesReceived = [] | ||
const firstClientSocketId = echoInstances[0].socketId(); | ||
await mockAuthServer.broadcastToOthers(firstClientSocketId, | ||
{ channelName: `private:${channelName}`, eventName: "toOthers", payload: "data" }); | ||
await waitForExpect(() => { | ||
expect(messagesReceived.length).toBe(1); | ||
expect(messagesReceived.filter(m => m == ".toOthers").length).toBe(1); | ||
}); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,28 +1,49 @@ | ||
import { isNullOrUndefinedOrEmpty, parseJwt } from '../../../src/channel/ably/utils'; | ||
import { isNullOrUndefinedOrEmpty, parseJwt, fromBase64UrlEncoded } from '../../../src/channel/ably/utils'; | ||
import * as Ably from 'ably/promises'; | ||
import * as jwt from 'jsonwebtoken'; | ||
|
||
type channels = Array<string>; | ||
|
||
/** | ||
* MockAuthServer mimicks {@link https://github.com/ably/laravel-broadcaster/blob/main/src/AblyBroadcaster.php AblyBroadcaster.php}. | ||
* Aim is to keep implementation and behaviour in sync with AblyBroadcaster.php, so that it can be tested | ||
* without running actual PHP server. | ||
*/ | ||
export class MockAuthServer { | ||
keyName: string; | ||
keySecret: string; | ||
ablyClient: Ably.Rest; | ||
clientId: string | null = '[email protected]'; | ||
userInfo = { id: '[email protected]', name: 'sacOO7' }; | ||
|
||
shortLived: channels; | ||
banned: channels; | ||
|
||
userInfo = { id: '[email protected]', name: 'sacOO7' }; // Used for presence | ||
|
||
constructor(apiKey: string, environment = 'sandbox') { | ||
const keys = apiKey.split(':'); | ||
this.keyName = keys[0]; | ||
this.keySecret = keys[1]; | ||
this.ablyClient = new Ably.Rest({ key: apiKey, environment }); | ||
} | ||
|
||
/** | ||
* Broadcast to all clients subscribed to given channel. | ||
*/ | ||
broadcast = async (channelName: string, eventName: string, message: string) => { | ||
await this.ablyClient.channels.get(channelName).publish(eventName, message); | ||
await this.broadcastToOthers("", {channelName, eventName, payload: message}); | ||
}; | ||
|
||
/** | ||
* Broadcast on behalf of a given realtime client. | ||
*/ | ||
broadcastToOthers = async (socketId: string, {channelName, eventName, payload}) => { | ||
let protoMsg = {name: eventName, data: payload}; | ||
if (!isNullOrUndefinedOrEmpty(socketId)) { | ||
const socketIdObj = JSON.parse(fromBase64UrlEncoded(socketId)); | ||
protoMsg = {...protoMsg, ...socketIdObj} | ||
} | ||
await this.ablyClient.channels.get(channelName).publish(protoMsg); | ||
}; | ||
|
||
tokenInvalidOrExpired = (serverTime, token) => { | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.