pubnub
Version:
Publish & Subscribe Real-time Messaging with PubNub
158 lines (143 loc) • 5.04 kB
text/typescript
/**
* Receiving real-time updates (connected) state module.
*
* @internal
*/
import { Effects, emitMessages, emitStatus, receiveMessages } from '../effects';
import {
disconnect,
Events,
receiveFailure,
receiveSuccess,
restore,
subscriptionChange,
unsubscribeAll,
} from '../events';
import * as Subscription from '../../core/types/api/subscription';
import categoryConstants from '../../core/constants/categories';
import { PubNubAPIError } from '../../errors/pubnub-api-error';
import RequestOperation from '../../core/constants/operations';
import { referenceSubscribeTimetoken } from '../../core/utils';
import { ReceiveStoppedState } from './receive_stopped';
import { ReceiveFailedState } from './receive_failed';
import { UnsubscribedState } from './unsubscribed';
import { State } from '../core/state';
/**
* Context which represent current Subscription Event Engine data state.
*
* @internal
*/
export type ReceivingStateContext = {
channels: string[];
groups: string[];
cursor: Subscription.SubscriptionCursor;
referenceTimetoken?: string;
onDemand?: boolean;
};
/**
* Receiving real-time updates (connected) state.
*
* State in which Subscription Event Engine processes any real-time updates.
*
* @internal
*/
export const ReceivingState = new State<ReceivingStateContext, Events, Effects>('RECEIVING');
ReceivingState.onEnter((context) =>
receiveMessages(context.channels, context.groups, context.cursor, context.onDemand ?? false),
);
ReceivingState.onExit(() => receiveMessages.cancel);
ReceivingState.on(receiveSuccess.type, (context, { payload }) =>
ReceivingState.with(
{
channels: context.channels,
groups: context.groups,
cursor: payload.cursor,
referenceTimetoken: referenceSubscribeTimetoken(payload.cursor.timetoken),
},
[emitMessages(context.cursor, payload.events)],
),
);
ReceivingState.on(subscriptionChange.type, (context, { payload }) => {
if (payload.channels.length === 0 && payload.groups.length === 0) {
let errorCategory: string | undefined;
if (payload.isOffline)
errorCategory = PubNubAPIError.create(new Error('Network connection error')).toPubNubError(
RequestOperation.PNSubscribeOperation,
).status?.category;
return UnsubscribedState.with(undefined, [
emitStatus({
category: !payload.isOffline
? categoryConstants.PNDisconnectedCategory
: categoryConstants.PNDisconnectedUnexpectedlyCategory,
...(errorCategory ? { error: errorCategory } : {}),
}),
]);
}
return ReceivingState.with(
{
channels: payload.channels,
groups: payload.groups,
cursor: context.cursor,
referenceTimetoken: context.referenceTimetoken,
onDemand: true,
},
[
emitStatus({
category: categoryConstants.PNSubscriptionChangedCategory,
affectedChannels: payload.channels.slice(0),
affectedChannelGroups: payload.groups.slice(0),
currentTimetoken: context.cursor.timetoken,
}),
],
);
});
ReceivingState.on(restore.type, (context, { payload }) => {
if (payload.channels.length === 0 && payload.groups.length === 0)
return UnsubscribedState.with(undefined, [emitStatus({ category: categoryConstants.PNDisconnectedCategory })]);
return ReceivingState.with(
{
channels: payload.channels,
groups: payload.groups,
cursor: { timetoken: `${payload.cursor.timetoken}`, region: payload.cursor.region || context.cursor.region },
referenceTimetoken: referenceSubscribeTimetoken(
context.cursor.timetoken,
`${payload.cursor.timetoken}`,
context.referenceTimetoken,
),
onDemand: true,
},
[
emitStatus({
category: categoryConstants.PNSubscriptionChangedCategory,
affectedChannels: payload.channels.slice(0),
affectedChannelGroups: payload.groups.slice(0),
currentTimetoken: payload.cursor.timetoken,
}),
],
);
});
ReceivingState.on(receiveFailure.type, (context, { payload }) =>
ReceiveFailedState.with({ ...context, reason: payload }, [
emitStatus({ category: categoryConstants.PNDisconnectedUnexpectedlyCategory, error: payload.status?.category }),
]),
);
ReceivingState.on(disconnect.type, (context, event) => {
if (!event.payload.isOffline) {
return ReceiveStoppedState.with({ ...context }, [
emitStatus({ category: categoryConstants.PNDisconnectedCategory }),
]);
} else {
const errorReason = PubNubAPIError.create(new Error('Network connection error')).toPubNubError(
RequestOperation.PNSubscribeOperation,
);
return ReceiveFailedState.with({ ...context, reason: errorReason }, [
emitStatus({
category: categoryConstants.PNDisconnectedUnexpectedlyCategory,
error: errorReason.status?.category,
}),
]);
}
});
ReceivingState.on(unsubscribeAll.type, (_) =>
UnsubscribedState.with(undefined, [emitStatus({ category: categoryConstants.PNDisconnectedCategory })]),
);