pubnub
Version:
Publish & Subscribe Real-time Messaging with PubNub
257 lines (217 loc) • 6.75 kB
text/typescript
/**
* Node.js {@link PubNub} File object module.
*/
import { Readable, PassThrough } from 'stream';
import { Buffer } from 'buffer';
import { basename } from 'path';
import fs from 'fs';
import { PubNubFileInterface } from '../../core/types/file';
// --------------------------------------------------------
// ------------------------ Types -------------------------
// --------------------------------------------------------
// region Types
/**
* PubNub File instance creation parameters.
*/
export type PubNubFileParameters<StringEncoding extends BufferEncoding = BufferEncoding> = {
/**
* Readable stream represents file object content.
*/
stream?: Readable;
/**
* Buffer or string represents file object content.
*/
data?: Buffer | ArrayBuffer | string;
/**
* String {@link PubNubFileParameters#data|data} encoding.
*
* @default `utf8`
*/
encoding?: StringEncoding;
/**
* File object name.
*/
name: string;
/**
* File object content type.
*/
mimeType?: string;
};
// endregion
/**
* Node.js implementation for {@link PubNub} File object.
*
* **Important:** Class should implement constructor and class fields from {@link PubNubFileConstructor}.
*/
export default class PubNubFile implements PubNubFileInterface {
// region Class properties
/**
* Whether {@link Blob} data supported by platform or not.
*/
static supportsBlob = false;
/**
* Whether {@link File} data supported by platform or not.
*/
static supportsFile = false;
/**
* Whether {@link Buffer} data supported by platform or not.
*/
static supportsBuffer = true;
/**
* Whether {@link Stream} data supported by platform or not.
*/
static supportsStream = true;
/**
* Whether {@link String} data supported by platform or not.
*/
static supportsString = true;
/**
* Whether {@link ArrayBuffer} supported by platform or not.
*/
static supportsArrayBuffer = true;
/**
* Whether {@link PubNub} File object encryption supported or not.
*/
static supportsEncryptFile = true;
/**
* Whether `File Uri` data supported by platform or not.
*/
static supportsFileUri = false;
// endregion
// region Instance properties
/**
* File object content source.
*/
readonly data: Readable | Buffer;
/**
* File object content length.
*/
contentLength?: number;
/**
* File object content type.
*/
mimeType: string;
/**
* File object name.
*/
name: string;
// endregion
static create(file: PubNubFileParameters) {
return new PubNubFile(file);
}
constructor(file: PubNubFileParameters) {
const { stream, data, encoding, name, mimeType } = file;
let fileData: Readable | Buffer | undefined;
let contentLength: number | undefined;
let fileMimeType: string | undefined;
let fileName: string | undefined;
if (stream && stream instanceof Readable) {
fileData = stream;
if (stream instanceof fs.ReadStream && typeof stream.path === 'string') {
fileName = basename(stream.path);
contentLength = fs.statSync(stream.path).size;
}
} else if (data instanceof Buffer) {
contentLength = data.length;
// Copy content of the source Buffer.
fileData = Buffer.alloc(contentLength!);
data.copy(fileData);
} else if (data instanceof ArrayBuffer) {
contentLength = data.byteLength;
fileData = Buffer.from(data);
} else if (typeof data === 'string') {
fileData = Buffer.from(data, encoding ?? 'utf8');
contentLength = fileData.length;
}
if (contentLength) this.contentLength = contentLength;
if (mimeType) fileMimeType = mimeType;
else fileMimeType = 'application/octet-stream';
if (name) fileName = basename(name);
if (fileData === undefined) throw new Error("Couldn't construct a file out of supplied options.");
if (fileName === undefined) throw new Error("Couldn't guess filename out of the options. Please provide one.");
this.mimeType = fileMimeType;
this.data = fileData;
this.name = fileName;
}
/**
* Convert {@link PubNub} File object content to {@link Buffer}.
*
* @returns Asynchronous results of conversion to the {@link Buffer}.
*/
async toBuffer(): Promise<Buffer> {
if (!(this.data instanceof Readable)) return this.data;
const stream = this.data;
return new Promise((resolve, reject) => {
const chunks: Uint8Array[] = [];
stream.on('data', (chunk) => {
chunks.push(chunk);
});
stream.on('end', () => {
resolve(Buffer.concat(chunks));
});
// Handle any errors during streaming
stream.on('error', (error) => reject(error));
});
}
/**
* Convert {@link PubNub} File object content to {@link ArrayBuffer}.
*
* @returns Asynchronous results of conversion to the {@link ArrayBuffer}.
*/
async toArrayBuffer(): Promise<ArrayBuffer> {
return this.toBuffer().then((buffer) => buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.length));
}
/**
* Convert {@link PubNub} File object content to {@link string}.
*
* @returns Asynchronous results of conversion to the {@link string}.
*/
async toString(encoding: BufferEncoding = 'utf8'): Promise<string> {
return this.toBuffer().then((buffer) => buffer.toString(encoding));
}
/**
* Convert {@link PubNub} File object content to {@link Readable} stream.
*
* @returns Asynchronous results of conversion to the {@link Readable} stream.
*/
async toStream() {
if (this.data instanceof Readable) {
const stream = new PassThrough();
this.data.pipe(stream);
return stream;
}
return this.toBuffer().then(
(buffer) =>
new Readable({
read() {
this.push(buffer);
this.push(null);
},
}),
);
}
/**
* Convert {@link PubNub} File object content to {@link File}.
*
* @throws Error because {@link File} not available in Node.js environment.
*/
async toFile() {
throw new Error('This feature is only supported in browser environments.');
}
/**
* Convert {@link PubNub} File object content to file `Uri`.
*
* @throws Error because file `Uri` not available in Node.js environment.
*/
async toFileUri(): Promise<Record<string, unknown>> {
throw new Error('This feature is only supported in React Native environments.');
}
/**
* Convert {@link PubNub} File object content to {@link Blob}.
*
* @throws Error because {@link Blob} not available in Node.js environment.
*/
async toBlob() {
throw new Error('This feature is only supported in browser environments.');
}
}