pubnub
Version:
Publish & Subscribe Real-time Messaging with PubNub
179 lines (157 loc) • 7.53 kB
text/typescript
import { TransportRequest } from '../../../core/types/transport-request';
import uuidGenerator from '../../../core/components/uuid';
import { BasePubNubRequest } from './request';
import { Payload } from '../../../core/types/api';
import { AccessToken } from './access-token';
export class HeartbeatRequest extends BasePubNubRequest {
// --------------------------------------------------------
// ---------------------- Information ---------------------
// --------------------------------------------------------
// region Information
/**
* Presence state associated with `userID` on {@link PubNubSharedWorkerRequest.channels|channels} and
* {@link PubNubSharedWorkerRequest.channelGroups|channelGroups}.
*/
readonly state: Record<string, Payload> | undefined;
// endregion
// --------------------------------------------------------
// --------------------- Constructors ---------------------
// --------------------------------------------------------
// region Constructors
/**
* Create heartbeat request from received _transparent_ transport request.
*
* @param request - Object with heartbeat transport request.
* @param subscriptionKey - Subscribe REST API access key.
* @param [accessToken] - Access token with permissions to announce presence on
* {@link PubNubSharedWorkerRequest.channels|channels} and
* {@link PubNubSharedWorkerRequest.channelGroups|channelGroups}.
* @returns Initialized and ready to use heartbeat request.
*/
static fromTransportRequest(request: TransportRequest, subscriptionKey: string, accessToken?: AccessToken) {
return new HeartbeatRequest(request, subscriptionKey, accessToken);
}
/**
* Create heartbeat request from previously cached data.
*
* @param request - Object with subscribe transport request.
* @param subscriptionKey - Subscribe REST API access key.
* @param [aggregatedChannelGroups] - List of aggregated channel groups for the same user.
* @param [aggregatedChannels] - List of aggregated channels for the same user.
* @param [aggregatedState] - State aggregated for the same user.
* @param [accessToken] - Access token with read permissions on
* {@link BasePubNubRequest.channels|channels} and
* {@link BasePubNubRequest.channelGroups|channelGroups}.
* @retusns Initialized and ready to use heartbeat request.
*/
static fromCachedState(
request: TransportRequest,
subscriptionKey: string,
aggregatedChannelGroups: string[],
aggregatedChannels: string[],
aggregatedState?: Record<string, Payload>,
accessToken?: AccessToken,
) {
// Update request channels list (if required).
if (aggregatedChannels.length || aggregatedChannelGroups.length) {
const pathComponents = request.path.split('/');
pathComponents[6] = aggregatedChannels.length ? [...aggregatedChannels].sort().join(',') : ',';
request.path = pathComponents.join('/');
}
// Update request channel groups list (if required).
if (aggregatedChannelGroups.length)
request.queryParameters!['channel-group'] = [...aggregatedChannelGroups].sort().join(',');
// Update request `state` (if required).
if (aggregatedState && Object.keys(aggregatedState).length)
request.queryParameters!.state = JSON.stringify(aggregatedState);
else delete request.queryParameters!.state;
if (accessToken) request.queryParameters!.auth = accessToken.toString();
request.identifier = uuidGenerator.createUUID();
return new HeartbeatRequest(request, subscriptionKey, accessToken);
}
/**
* Create heartbeat request from received _transparent_ transport request.
*
* @param request - Object with heartbeat transport request.
* @param subscriptionKey - Subscribe REST API access key.
* @param [accessToken] - Access token with permissions to announce presence on
* {@link PubNubSharedWorkerRequest.channels|channels} and
* {@link PubNubSharedWorkerRequest.channelGroups|channelGroups}.
*/
private constructor(request: TransportRequest, subscriptionKey: string, accessToken?: AccessToken) {
const channelGroups = HeartbeatRequest.channelGroupsFromRequest(request).filter(
(group) => !group.endsWith('-pnpres'),
);
const channels = HeartbeatRequest.channelsFromRequest(request).filter((channel) => !channel.endsWith('-pnpres'));
super(request, subscriptionKey, request.queryParameters!.uuid as string, channels, channelGroups, accessToken);
// Clean up `state` from objects which is not used with request (if needed).
if (!request.queryParameters!.state || (request.queryParameters!.state as string).length === 0) return;
const state = JSON.parse(request.queryParameters!.state as string) as Record<string, Payload>;
for (const objectName of Object.keys(state))
if (!this.channels.includes(objectName) && !this.channelGroups.includes(objectName)) delete state[objectName];
this.state = state;
}
// endregion
// --------------------------------------------------------
// ---------------------- Properties ----------------------
// --------------------------------------------------------
// region Properties
/**
* Represent heartbeat request as identifier.
*
* Generated identifier will be identical for requests created for the same user.
*/
get asIdentifier() {
const auth = this.accessToken ? this.accessToken.asIdentifier : undefined;
return `${this.userId}-${this.subscribeKey}${auth ? `-${auth}` : ''}`;
}
// endregion
// --------------------------------------------------------
// ----------------------- Helpers ------------------------
// --------------------------------------------------------
// region Helpers
/**
* Serialize request for easier representation in logs.
*
* @returns Stringified `heartbeat` request.
*/
toString() {
return `HeartbeatRequest { channels: [${
this.channels.length ? this.channels.map((channel) => `'${channel}'`).join(', ') : ''
}], channelGroups: [${
this.channelGroups.length ? this.channelGroups.map((group) => `'${group}'`).join(', ') : ''
}] }`;
}
/**
* Serialize request to "typed" JSON string.
*
* @returns "Typed" JSON string.
*/
toJSON() {
return this.toString();
}
/**
* Extract list of channels for presence announcement from request URI path.
*
* @param request - Transport request from which should be extracted list of channels for presence announcement.
*
* @returns List of channel names (not percent-decoded) for which `heartbeat` has been called.
*/
private static channelsFromRequest(request: TransportRequest): string[] {
const channels = request.path.split('/')[6];
return channels === ',' ? [] : channels.split(',').filter((name) => name.length > 0);
}
/**
* Extract list of channel groups for presence announcement from request query.
*
* @param request - Transport request from which should be extracted list of channel groups for presence announcement.
*
* @returns List of channel group names (not percent-decoded) for which `heartbeat` has been called.
*/
private static channelGroupsFromRequest(request: TransportRequest): string[] {
if (!request.queryParameters || !request.queryParameters['channel-group']) return [];
const group = request.queryParameters['channel-group'] as string;
return group.length === 0 ? [] : group.split(',').filter((name) => name.length > 0);
}
// endregion
}