UNPKG

pubnub

Version:

Publish & Subscribe Real-time Messaging with PubNub

618 lines (558 loc) 23 kB
import { expect } from 'chai'; import nock from 'nock'; import * as Subscription from '../../../src/core/types/api/subscription'; import PubNub from '../../../src/node/index'; import utils from '../../utils'; let pubnub: PubNub; describe('#listeners', () => { before(() => { nock.disableNetConnect(); }); beforeEach(() => { nock.cleanAll(); pubnub = new PubNub({ subscribeKey: 'mySubKey', publishKey: 'myPublishKey', origin: 'ps.pndsn.com', uuid: 'myUUID', // @ts-expect-error Force override default value. useRequestId: false, enableEventEngine: true, autoNetworkDetection: false, // logLevel: PubNub.LogLevel.Trace, }); }); afterEach(() => { pubnub.destroy(true); }); it('should pass messages of subscribed channel to its listener', async () => { utils.createPresenceMockScopes({ subKey: 'mySubKey', presenceType: 'heartbeat', requests: [{ channels: ['ch1'] }], }); utils.createSubscribeMockScopes({ subKey: 'mySubKey', pnsdk: `PubNub-JS-Nodejs/${pubnub.getVersion()}`, userId: 'myUUID', eventEngine: true, requests: [ { channels: ['ch1'], messages: [{ channel: 'ch1', message: { message: 'My message!' } }] }, { channels: ['ch1'], messages: [], replyDelay: 500 }, ], }); const channel = pubnub.channel('ch1'); const subscription = channel.subscription(); const messagePromise = new Promise<Subscription.Message>((resolveMessage) => subscription.addListener({ message: (m) => resolveMessage(m), }), ); subscription.subscribe(); const actual = await messagePromise; expect(JSON.stringify(actual.message)).to.equal('{"message":"My message!"}'); }); it('should subscribed to channel and presence channels', async () => { utils.createPresenceMockScopes({ subKey: 'mySubKey', presenceType: 'heartbeat', requests: [{ channels: ['ch1'] }], }); utils.createSubscribeMockScopes({ subKey: 'mySubKey', pnsdk: `PubNub-JS-Nodejs/${pubnub.getVersion()}`, userId: 'myUUID', eventEngine: true, requests: [ { channels: ['ch1', 'ch1-pnpres'], messages: [{ channel: 'ch1', message: { message: 'My message!' } }] }, { channels: ['ch1', 'ch1-pnpres'], messages: [], replyDelay: 500 }, ], }); nock.enableNetConnect(); const channel = pubnub.channel('ch1'); const subscription = channel.subscription({ receivePresenceEvents: true }); const messagePromise = new Promise<Subscription.Message>((resolveMessage) => subscription.addListener({ message: (m) => resolveMessage(m), }), ); subscription.subscribe(); const actual = await messagePromise; expect(JSON.stringify(actual.message)).to.equal('{"message":"My message!"}'); }); it('should work with subscriptionSet', async () => { utils.createPresenceMockScopes({ subKey: 'mySubKey', presenceType: 'heartbeat', requests: [{ channels: ['ch1', 'ch2'] }], }); utils.createSubscribeMockScopes({ subKey: 'mySubKey', pnsdk: `PubNub-JS-Nodejs/${pubnub.getVersion()}`, userId: 'myUUID', eventEngine: true, requests: [ { channels: ['ch1', 'ch2'], messages: [{ channel: 'ch2', message: { message: 'My message!' } }] }, { channels: ['ch1', 'ch2'], messages: [], replyDelay: 500 }, ], }); const channel = pubnub.channel('ch1'); const subscription = channel.subscription(); const subscriptionSet = subscription.addSubscription(pubnub.channel('ch2').subscription()); const messagePromise = new Promise<Subscription.Message>((resolveMessage) => subscriptionSet.addListener({ message: (m) => resolveMessage(m), }), ); subscriptionSet.subscribe(); const actual = await messagePromise; expect(JSON.stringify(actual.message)).to.equal('{"message":"My message!"}'); }); it('should able to create subscriptionSet', async () => { utils.createPresenceMockScopes({ subKey: 'mySubKey', presenceType: 'heartbeat', requests: [{ channels: ['ch1', 'ch2'] }], }); utils.createSubscribeMockScopes({ subKey: 'mySubKey', pnsdk: `PubNub-JS-Nodejs/${pubnub.getVersion()}`, userId: 'myUUID', eventEngine: true, requests: [ { channels: ['ch1', 'ch2'], messages: [{ channel: 'ch2', message: { message: 'My message!' } }] }, { channels: ['ch1', 'ch2'], messages: [], replyDelay: 500 }, ], }); const subscriptionSet = pubnub.subscriptionSet({ channels: ['ch1', 'ch2'] }); const messagePromise = new Promise<Subscription.Message>((resolveMessage) => subscriptionSet.addListener({ message: (m) => resolveMessage(m), }), ); subscriptionSet.subscribe(); const actual = await messagePromise; expect(JSON.stringify(actual.message)).to.equal('{"message":"My message!"}'); }); it('subscriptionSet works with add/remove with set', async () => { utils.createPresenceMockScopes({ subKey: 'mySubKey', presenceType: 'heartbeat', requests: [{ channels: ['ch3', 'ch4'] }], }); utils.createSubscribeMockScopes({ subKey: 'mySubKey', pnsdk: `PubNub-JS-Nodejs/${pubnub.getVersion()}`, userId: 'myUUID', eventEngine: true, requests: [ { channels: ['ch3', 'ch4'], messages: [{ channel: 'ch3', message: { message: 'My message!' } }] }, { channels: ['ch3', 'ch4'], messages: [], replyDelay: 500 }, ], }); const subscriptionSetCh34 = pubnub.subscriptionSet({ channels: ['ch3', 'ch4'] }); const subscriptionSetCh12 = pubnub .channel('ch1') .subscription() .addSubscription(pubnub.channel('ch2').subscription()); subscriptionSetCh34.addSubscriptionSet(subscriptionSetCh12); subscriptionSetCh34.removeSubscriptionSet(subscriptionSetCh12); const messagePromise = new Promise<Subscription.Message>((resolveMessage) => subscriptionSetCh34.addListener({ message: (m) => resolveMessage(m), }), ); subscriptionSetCh34.subscribe(); const actual = await messagePromise; expect(JSON.stringify(actual.message)).to.equal('{"message":"My message!"}'); }); it('listener should route presence event to registered handler', async () => { utils.createPresenceMockScopes({ subKey: 'mySubKey', presenceType: 'heartbeat', requests: [{ channels: ['ch1'] }], }); utils.createSubscribeMockScopes({ subKey: 'mySubKey', pnsdk: `PubNub-JS-Nodejs/${pubnub.getVersion()}`, userId: 'myUUID', eventEngine: true, requests: [ { channels: ['ch1', 'ch1-pnpres'], messages: [{ channel: 'ch1-pnpres', presenceAction: 'join', presenceOccupancy: 2 }], }, { channels: ['ch1', 'ch1-pnpres'], messages: [], replyDelay: 500 }, ], }); const channel = pubnub.channel('ch1'); const subscription = channel.subscription({ receivePresenceEvents: true }); const presencePromise = new Promise<Subscription.Presence>((resolvePresence) => subscription.addListener({ presence: (p) => resolvePresence(p), }), ); subscription.subscribe(); const actual = await presencePromise; if (actual.action === 'join') { expect(actual.occupancy).to.equal(2); } else throw new Error('Unexpected presence event'); }); it('add/remove listener should work on subscription', async () => { utils.createPresenceMockScopes({ subKey: 'mySubKey', presenceType: 'heartbeat', requests: [{ channels: ['ch1'] }], }); utils.createSubscribeMockScopes({ subKey: 'mySubKey', pnsdk: `PubNub-JS-Nodejs/${pubnub.getVersion()}`, userId: 'myUUID', eventEngine: true, requests: [ { channels: ['ch1'], messages: [{ channel: 'ch1', message: { message: 'My message!' } }] }, { channels: ['ch1'], messages: [], replyDelay: 500 }, ], }); const messages: Subscription.Message[] = []; const subscription = pubnub.channel('ch1').subscription(); const listener = { message: (m: Subscription.Message) => messages.push(m) }; subscription.addListener(listener); const messagePromise = new Promise<Subscription.Message>((resolveMessage) => { subscription.removeListener(listener); subscription.addListener({ message: (m) => resolveMessage(m), }); }); subscription.subscribe(); const actual = await messagePromise; expect(JSON.stringify(actual.message)).to.equal('{"message":"My message!"}'); expect(messages.length).to.equal(0); }); it('should work with channel groups and their presence', async () => { utils.createPresenceMockScopes({ subKey: 'mySubKey', presenceType: 'heartbeat', requests: [{ groups: ['cg1'] }] }); utils.createSubscribeMockScopes({ subKey: 'mySubKey', pnsdk: `PubNub-JS-Nodejs/${pubnub.getVersion()}`, userId: 'myUUID', eventEngine: true, requests: [ { groups: ['cg1', 'cg1-pnpres'], messages: [{ channel: 'ch1', group: 'cg1', message: { message: 'My message!' } }], }, { groups: ['cg1', 'cg1-pnpres'], messages: [], replyDelay: 500 }, ], }); const channelGroup = pubnub.channelGroup('cg1'); const subscription = channelGroup.subscription({ receivePresenceEvents: true }); const messagePromise = new Promise<Subscription.Message>((resolveMessage) => subscription.addListener({ message: (m) => resolveMessage(m), }), ); subscription.subscribe(); const actual = await messagePromise; expect(JSON.stringify(actual.message)).to.equal('{"message":"My message!"}'); }); it('subscribe/unsubscribe handle edge case of having overlying channel/group set', async () => { utils.createPresenceMockScopes({ subKey: 'mySubKey', presenceType: 'heartbeat', requests: [{ channels: ['ch1'] }, { channels: ['ch1', 'ch2'] }, { channels: ['ch1', 'ch2', 'ch3'] }], }); utils.createPresenceMockScopes({ subKey: 'mySubKey', presenceType: 'leave', requests: [{ channels: ['ch1'] }, { channels: ['ch2', 'ch3'] }], }); utils.createSubscribeMockScopes({ subKey: 'mySubKey', pnsdk: `PubNub-JS-Nodejs/${pubnub.getVersion()}`, userId: 'myUUID', eventEngine: true, requests: [ { channels: ['ch1'], messages: [{ channel: 'ch1', message: { message: 'My message!' } }] }, { channels: ['ch1'], messages: [{ channel: 'ch1', message: { message: 'My message!' } }] }, { channels: ['ch1', 'ch2'], messages: [{ channel: 'ch1', message: { message: 'My message!' } }] }, { channels: ['ch1', 'ch2', 'ch3'], messages: [{ channel: 'ch1', message: { message: 'My message!' } }] }, { channels: ['ch2', 'ch3'], messages: [{ channel: 'ch2', message: { ch2: 'My message!' }, timetokenAdjust: '10000000' }], }, { channels: ['ch2', 'ch3'], messages: [], replyDelay: 500 }, ], }); const messages: Subscription.Message[] = []; const channel = pubnub.channel('ch1'); const subscription = channel.subscription(); const listener = { message: (m: Subscription.Message) => messages.push(m) }; subscription.addListener(listener); const messagePromise = new Promise<Subscription.Message>((resolveMessage) => { subscription.removeListener(listener); subscription.addListener({ message: (m) => resolveMessage(m), }); }); subscription.subscribe(); const actual = await messagePromise; expect(JSON.stringify(actual.message)).to.equal('{"message":"My message!"}'); expect(messages.length).to.equal(0); const subscriptionCh2 = pubnub.channel('ch2').subscription(); subscriptionCh2.subscribe(); const subscriptionCh3 = pubnub.channel('ch3').subscription(); const subscriptionSetCh23 = subscriptionCh3.addSubscription(pubnub.channel('ch2').subscription()); const messagePromiseChannel2 = new Promise<Subscription.Message>((resolveMessage) => subscriptionSetCh23.addListener({ message: (m) => resolveMessage(m), }), ); subscriptionSetCh23.subscribe(); subscription.unsubscribe(); subscriptionCh2.unsubscribe(); const actualChannel2MessageAfterOneUnsubCh2 = await messagePromiseChannel2; pubnub.destroy(); expect(JSON.stringify(actualChannel2MessageAfterOneUnsubCh2.message)).to.equal('{"ch2":"My message!"}'); }); it("subscribe and don't deliver old messages", async () => { utils.createPresenceMockScopes({ subKey: 'mySubKey', presenceType: 'heartbeat', requests: [{ channels: ['ch1'] }, { channels: ['ch1', 'ch2'] }], }); utils.createSubscribeMockScopes({ subKey: 'mySubKey', pnsdk: `PubNub-JS-Nodejs/${pubnub.getVersion()}`, userId: 'myUUID', eventEngine: true, requests: [ { channels: ['ch1'], messages: [{ channel: 'ch1', message: { message: 'My message!' } }] }, { channels: ['ch1', 'ch2'], messages: [ { channel: 'ch1', message: { message: 'My message!' } }, { channel: 'ch2', message: { ch2: 'My old message!' }, timetokenAdjust: '-5000000' }, ], }, { channels: ['ch1', 'ch2'], messages: [{ channel: 'ch2', message: { ch2: 'My new message!' }, timetokenAdjust: '10000000' }], }, { channels: ['ch1', 'ch2'], messages: [], replyDelay: 500 }, ], }); const messages: Subscription.Message[] = []; const subscriptionCh1 = pubnub.channel('ch1').subscription(); subscriptionCh1.onMessage = () => {}; const connectionPromise = new Promise<void>((resolve) => { pubnub.onStatus = (status) => { if (status.category === PubNub.CATEGORIES.PNConnectedCategory) resolve(); }; }); // Wait for connection. subscriptionCh1.subscribe(); await connectionPromise; const subscriptionCh2 = pubnub.channel('ch2').subscription(); const messagePromise = new Promise<void>((resolveMessage) => { subscriptionCh2.onMessage = (message) => messages.push(message); setTimeout(() => resolveMessage(), 500); }); // Wait for messages. subscriptionCh2.subscribe(); await messagePromise; expect(messages.length).to.equal(1); expect(JSON.stringify(messages[0].message)).to.equal('{"ch2":"My new message!"}'); }); /// `17525257097772389` and `17525257156893384` used on purpose because this combination /// was the root of the issue (when catch up timetoken is larger than the one provided by service response). it('subscribe and deliver messages when server returns old timetoken', async () => { utils.createPresenceMockScopes({ subKey: 'mySubKey', presenceType: 'heartbeat', requests: [{ channels: ['ch1'] }, { channels: ['ch1'] }, { channels: ['ch1', 'ch2'] }], }); utils.createSubscribeMockScopes({ subKey: 'mySubKey', pnsdk: `PubNub-JS-Nodejs/${pubnub.getVersion()}`, userId: 'myUUID', eventEngine: true, requests: [{ channels: ['ch1'], messages: [], initialTimetokenOverride: '17525257097772389' }], }); utils.createSubscribeMockScopes({ subKey: 'mySubKey', pnsdk: `PubNub-JS-Nodejs/${pubnub.getVersion()}`, userId: 'myUUID', eventEngine: true, requests: [ { channels: ['ch1'], messages: [], initialTimetokenOverride: '17525257097772389' }, { channels: ['ch1', 'ch2'], messages: [{ channel: 'ch2', message: { ch2: 'My expected message!' } }], timetoken: '17525257156893384', }, { channels: ['ch1', 'ch2'], messages: [], replyDelay: 500 }, ], }); const messages: Subscription.Message[] = []; const subscriptionCh1 = pubnub.channel('ch1').subscription(); subscriptionCh1.onMessage = () => {}; const connectionPromise = new Promise<void>((resolve) => { pubnub.onStatus = (status) => { if (status.category === PubNub.CATEGORIES.PNConnectedCategory) resolve(); }; }); // Wait for connection. subscriptionCh1.subscribe(); await connectionPromise; const disconnectionPromise = new Promise<void>((resolve) => { pubnub.onStatus = (status) => { // This is simulation which possible to reproduce only this way. if (status.category === PubNub.CATEGORIES.PNDisconnectedUnexpectedlyCategory) resolve(); }; }); // Wait for disconnection. pubnub.disconnect(true); await disconnectionPromise; const reconnectionPromise = new Promise<void>((resolve) => { pubnub.onStatus = (status) => { if (status.category === PubNub.CATEGORIES.PNConnectedCategory) resolve(); }; }); // Wait for reconnection. pubnub.reconnect({ timetoken: '17525257156893384' }); await reconnectionPromise; const subscriptionCh2 = pubnub.channel('ch2').subscription(); const messagePromise = new Promise<void>((resolveMessage) => { subscriptionCh2.onMessage = (message) => messages.push(message); setTimeout(() => resolveMessage(), 500); }); // Wait for messages. subscriptionCh2.subscribe(); await messagePromise; expect(messages.length).to.equal(1); expect(JSON.stringify(messages[0].message)).to.equal('{"ch2":"My expected message!"}'); }); it('subscribe and deliver notifications targeted by subscription object', async () => { utils.createPresenceMockScopes({ subKey: 'mySubKey', presenceType: 'heartbeat', requests: [ { channels: ['ch1', 'ch2'] }, { channels: ['ch1', 'ch2', 'ch3'] }, { channels: ['ch1', 'ch2', 'ch3', 'ch4'] }, { channels: ['ch1', 'ch2', 'ch3', 'ch4', 'ch5'] }, ], }); utils.createSubscribeMockScopes({ subKey: 'mySubKey', pnsdk: `PubNub-JS-Nodejs/${pubnub.getVersion()}`, userId: 'myUUID', eventEngine: true, requests: [ { channels: ['ch1', 'ch2'], messages: [{ channel: 'ch1', message: { message: 'My message!' } }] }, { channels: ['ch1', 'ch2', 'ch3'], messages: [] }, { channels: ['ch1', 'ch2', 'ch3', 'ch4'], messages: [] }, { channels: ['ch1', 'ch2', 'ch3', 'ch4', 'ch5'], messages: [ { channel: 'ch1', message: { message: 'My message!' } }, { channel: 'ch3', message: { ch3: 'My message!' }, timetokenAdjust: '10000000' }, ], }, { channels: ['ch1', 'ch2', 'ch3', 'ch4', 'ch5'], messages: [], replyDelay: 500 }, ], }); const ch12Messages: Subscription.Message[] = []; const ch3Messages: Subscription.Message[] = []; const subscriptionCh12 = pubnub.subscriptionSet({ channels: ['ch1', 'ch2'] }); subscriptionCh12.onMessage = (message) => ch12Messages.push(message); const connectionPromise = new Promise<void>((resolve) => { pubnub.onStatus = (status) => { if (status.category === PubNub.CATEGORIES.PNConnectedCategory) resolve(); }; }); // Wait for connection. subscriptionCh12.subscribe(); await connectionPromise; const subscriptionCh3 = pubnub.channel('ch3').subscription(); const messagePromise = new Promise<void>((resolveMessage) => { subscriptionCh3.onMessage = (message) => ch3Messages.push(message); setTimeout(() => resolveMessage(), 500); }); // Wait for messages. subscriptionCh3.subscribe(); const subscriptionCh4 = pubnub.channel('ch4').subscription(); const subscriptionSet1Ch34 = subscriptionCh4.addSubscription(subscriptionCh3); const subscriptionCh5 = pubnub.channel('ch5').subscription(); const subscriptionSet2Ch35 = subscriptionCh5.addSubscription(subscriptionCh3); expect(subscriptionCh4.state.isSubscribed).to.equal(true); expect(subscriptionSet1Ch34.state.isSubscribed).to.equal(true); expect(subscriptionCh5.state.isSubscribed).to.equal(true); expect(subscriptionSet2Ch35.state.isSubscribed).to.equal(true); await messagePromise; expect(ch12Messages.length).to.equal(1); expect(ch3Messages.length).to.equal(1); expect(JSON.stringify(ch12Messages[0].message)).to.equal('{"message":"My message!"}'); expect(JSON.stringify(ch3Messages[0].message)).to.equal('{"ch3":"My message!"}'); }); it('should work with event type specific listener registration', async () => { utils.createPresenceMockScopes({ subKey: 'mySubKey', presenceType: 'heartbeat', requests: [{ channels: ['ch1'] }], }); utils.createSubscribeMockScopes({ subKey: 'mySubKey', pnsdk: `PubNub-JS-Nodejs/${pubnub.getVersion()}`, userId: 'myUUID', eventEngine: true, requests: [ { channels: ['ch1'], messages: [{ channel: 'ch1', message: { message: 'My message!' } }] }, { channels: ['ch1'], messages: [], replyDelay: 500 }, ], }); const channel = pubnub.channel('ch1'); const subscription = channel.subscription(); const messagePromise = new Promise<Subscription.Message>( (resolveMessage) => (subscription.onMessage = (m) => resolveMessage(m)), ); subscription.subscribe(); const actual = await messagePromise; expect(JSON.stringify(actual.message)).to.equal('{"message":"My message!"}'); }); it('with presence should work with event type specific listener registration', async () => { utils.createPresenceMockScopes({ subKey: 'mySubKey', presenceType: 'heartbeat', requests: [{ channels: ['ch1'] }], }); utils.createSubscribeMockScopes({ subKey: 'mySubKey', pnsdk: `PubNub-JS-Nodejs/${pubnub.getVersion()}`, userId: 'myUUID', eventEngine: true, requests: [ { channels: ['ch1', 'ch1-pnpres'], messages: [{ channel: 'ch1-pnpres', presenceAction: 'join', presenceUserId: 'bob' }], }, { channels: ['ch1', 'ch1-pnpres'], messages: [], replyDelay: 500 }, ], }); const channel = pubnub.channel('ch1'); const subscription = channel.subscription({ receivePresenceEvents: true }); const presencePromise = new Promise<Subscription.Presence>( (resolvePresenceEvent) => (subscription.onPresence = (p) => resolvePresenceEvent(p)), ); subscription.subscribe(); const actual = await presencePromise; expect(actual.channel).to.be.equal('ch1'); expect(actual.subscription).to.be.equal('ch1-pnpres'); expect(actual.action).to.be.equal('join'); // @ts-expect-error: Don't check a type of presence event here. expect(actual.occupancy).to.be.equal(1); // @ts-expect-error: Don't check a type of presence event here. expect(actual.uuid).to.be.equal('bob'); expect(actual.timetoken).to.not.be.equal(undefined); }); });