pubnub
Version:
Publish & Subscribe Real-time Messaging with PubNub
221 lines (187 loc) • 6.35 kB
text/typescript
/**
* Publish REST API module.
*/
import { TransportResponse } from '../types/transport-response';
import { TransportMethod } from '../types/transport-request';
import { ICryptoModule } from '../interfaces/crypto-module';
import { AbstractRequest } from '../components/request';
import RequestOperation from '../constants/operations';
import { KeySet, Payload, Query } from '../types/api';
import { encode } from '../components/base64_codec';
import { encodeString } from '../utils';
// --------------------------------------------------------
// ----------------------- Defaults -----------------------
// --------------------------------------------------------
// region Defaults
/**
* Whether data is published used `POST` body or not.
*/
const SEND_BY_POST = false;
// endregion
// --------------------------------------------------------
// ------------------------ Types -------------------------
// --------------------------------------------------------
// region Types
/**
* Request configuration parameters.
*/
export type PublishParameters = {
/**
* Channel name to publish messages to.
*/
channel: string;
/**
* Data which should be sent to the `channel`.
*
* The message may be any valid JSON type including objects, arrays, strings, and numbers.
*/
message: Payload;
/**
* User-specified message type.
*
* **Important:** string limited by **3**-**50** case-sensitive alphanumeric characters with only
* `-` and `_` special characters allowed.
*/
customMessageType?: string;
/**
* Whether published data should be available with `Storage API` later or not.
*
* @default `true`
*/
storeInHistory?: boolean;
/**
* Whether message should be sent as part of request POST body or not.
*
* @default `false`
*/
sendByPost?: boolean;
/**
* Metadata, which should be associated with published data.
*
* Associated metadata can be utilized by message filtering feature.
*/
meta?: Payload;
/**
* Specify duration during which data will be available with `Storage API`.
*
* - If `storeInHistory` = `true`, and `ttl` = `0`, the `message` is stored with no expiry time.
* - If `storeInHistory` = `true` and `ttl` = `X` (`X` is an Integer value), the `message` is
* stored with an expiry time of `X` hours.
* - If `storeInHistory` = `false`, the `ttl` parameter is ignored.
* - If `ttl` is not specified, then expiration of the `message` defaults back to the expiry value
* for the key.
*/
ttl?: number;
/**
* Whether published data should be replicated across all data centers or not.
*
* @default `true`
* @deprecated
*/
replicate?: boolean;
};
/**
* Service success response.
*/
export type PublishResponse = {
/**
* High-precision time when published data has been received by the PubNub service.
*/
timetoken: string;
};
/**
* Request configuration parameters.
*/
type RequestParameters = PublishParameters & {
/**
* PubNub REST API access key set.
*/
keySet: KeySet;
/**
* Published data encryption module.
*/
crypto?: ICryptoModule;
};
/**
* Service success response.
*/
type ServiceResponse = [0 | 1, string, string];
// endregion
/**
* Data publish request.
*
* Request will normalize and encrypt (if required) provided data and push it to the specified
* channel.
*
* @internal
*/
export class PublishRequest extends AbstractRequest<PublishResponse, ServiceResponse> {
/**
* Construct data publish request.
*
* @param parameters - Request configuration.
*/
constructor(private readonly parameters: RequestParameters) {
const sendByPost = parameters.sendByPost ?? SEND_BY_POST;
super({ method: sendByPost ? TransportMethod.POST : TransportMethod.GET, compressible: sendByPost });
// Apply default request parameters.
this.parameters.sendByPost = sendByPost;
}
operation(): RequestOperation {
return RequestOperation.PNPublishOperation;
}
validate(): string | undefined {
const {
message,
channel,
keySet: { publishKey },
} = this.parameters;
if (!channel) return "Missing 'channel'";
if (!message) return "Missing 'message'";
if (!publishKey) return "Missing 'publishKey'";
}
async parse(response: TransportResponse): Promise<PublishResponse> {
return { timetoken: this.deserializeResponse(response)[2] };
}
protected get path(): string {
const { message, channel, keySet } = this.parameters;
const stringifiedPayload = this.prepareMessagePayload(message);
return `/publish/${keySet.publishKey}/${keySet.subscribeKey}/0/${encodeString(channel)}/0${
!this.parameters.sendByPost ? `/${encodeString(stringifiedPayload)}` : ''
}`;
}
protected get queryParameters(): Query {
const { customMessageType, meta, replicate, storeInHistory, ttl } = this.parameters;
const query: Query = {};
if (customMessageType) query.custom_message_type = customMessageType;
if (storeInHistory !== undefined) query.store = storeInHistory ? '1' : '0';
if (ttl !== undefined) query.ttl = ttl;
if (replicate !== undefined && !replicate) query.norep = 'true';
if (meta && typeof meta === 'object') query.meta = JSON.stringify(meta);
return query;
}
protected get headers(): Record<string, string> | undefined {
if (!this.parameters.sendByPost) return super.headers;
return { ...(super.headers ?? {}), 'Content-Type': 'application/json' };
}
protected get body(): ArrayBuffer | string | undefined {
return this.prepareMessagePayload(this.parameters.message);
}
/**
* Pre-process provided data.
*
* Data will be "normalized" and encrypted if `cryptoModule` has been provided.
*
* @param payload - User-provided data which should be pre-processed before use.
*
* @returns Payload which can be used as part of request URL or body.
*
* @throws {Error} in case if provided `payload` or results of `encryption` can't be stringified.
*/
private prepareMessagePayload(payload: Payload): string {
const { crypto } = this.parameters;
if (!crypto) return JSON.stringify(payload) || '';
const encrypted = crypto.encrypt(JSON.stringify(payload));
return JSON.stringify(typeof encrypted === 'string' ? encrypted : encode(encrypted));
}
}