UNPKG

pubnub

Version:

Publish & Subscribe Real-time Messaging with PubNub

206 lines (181 loc) 6.4 kB
/** * Share File API module. * * @internal */ import { PubNubFileConstructor, PubNubFileInterface } from '../../types/file'; import { GenerateFileUploadUrlRequest } from './generate_upload_url'; import { PubNubAPIError } from '../../../errors/pubnub-api-error'; import { ICryptoModule } from '../../interfaces/crypto-module'; import { Cryptography } from '../../interfaces/cryptography'; import { AbstractRequest } from '../../components/request'; import * as FileSharing from '../../types/api/file-sharing'; import { PubNubError } from '../../../errors/pubnub-error'; import RequestOperation from '../../constants/operations'; import StatusCategory from '../../constants/categories'; import { UploadFileRequest } from './upload-file'; import { KeySet } from '../../types/api'; // -------------------------------------------------------- // ------------------------ Types ------------------------- // -------------------------------------------------------- // region Types /** * Request configuration parameters. */ type RequestParameters<FileParameters> = FileSharing.SendFileParameters<FileParameters> & { /** * How many times should retry file message publish. */ fileUploadPublishRetryLimit: number; /** * PubNub REST API access key set. */ keySet: KeySet; /** * {@link PubNub} File object constructor. */ PubNubFile: PubNubFileConstructor<PubNubFileInterface, FileParameters>; /** * Request sending method. * * @param request - Request which should be processed. */ sendRequest: <ResponseType>(request: AbstractRequest<ResponseType, Record<string, unknown>>) => Promise<ResponseType>; /** * File message publish method. * * @param parameters - File message request parameters. */ publishFile: ( parameters: FileSharing.PublishFileMessageParameters, ) => Promise<FileSharing.PublishFileMessageResponse>; /** * If passed, will encrypt the payloads. * * @deprecated Pass it to {@link crypto} module instead. */ cipherKey?: string; /** * Published data encryption module. */ crypto?: ICryptoModule; /** * Legacy cryptography module. */ cryptography?: Cryptography<ArrayBuffer>; }; // endregion /** * Send file composed request. * * @internal */ export class SendFileRequest<FileConstructorParameters> { /** * File object for upload. */ private file?: PubNubFileInterface; constructor(private readonly parameters: RequestParameters<FileConstructorParameters>) { this.file = this.parameters.PubNubFile?.create(parameters.file); if (!this.file) throw new Error('File upload error: unable to create File object.'); } /** * Process user-input and upload file. * * @returns File upload request response. */ public async process(): Promise<FileSharing.SendFileResponse> { let fileName: string | undefined; let fileId: string | undefined; return this.generateFileUploadUrl() .then((result) => { fileName = result.name; fileId = result.id; return this.uploadFile(result); }) .then((result) => { if (result.status !== 204) { throw new PubNubError('Upload to bucket was unsuccessful', { error: true, statusCode: result.status, category: StatusCategory.PNUnknownCategory, operation: RequestOperation.PNPublishFileOperation, errorData: { message: result.message }, }); } }) .then(() => this.publishFileMessage(fileId!, fileName!)) .catch((error: Error) => { if (error instanceof PubNubError) throw error; const apiError = !(error instanceof PubNubAPIError) ? PubNubAPIError.create(error) : error; throw new PubNubError('File upload error.', apiError.toStatus(RequestOperation.PNPublishFileOperation)); }); } /** * Generate pre-signed file upload Url. * * @returns File upload credentials. */ private async generateFileUploadUrl(): Promise<FileSharing.GenerateFileUploadUrlResponse> { const request = new GenerateFileUploadUrlRequest({ ...this.parameters, name: this.file!.name, keySet: this.parameters.keySet, }); return this.parameters.sendRequest(request); } /** * Prepare and upload {@link PubNub} File object to remote storage. * * @param uploadParameters - File upload request parameters. * * @returns */ private async uploadFile(uploadParameters: FileSharing.GenerateFileUploadUrlResponse) { const { cipherKey, PubNubFile, crypto, cryptography } = this.parameters; const { id, name, url, formFields } = uploadParameters; // Encrypt file if possible. if (this.parameters.PubNubFile!.supportsEncryptFile) { if (!cipherKey && crypto) this.file = (await crypto.encryptFile(this.file!, PubNubFile!))!; else if (cipherKey && cryptography) this.file = (await cryptography.encryptFile(cipherKey, this.file!, PubNubFile!))!; } return this.parameters.sendRequest( new UploadFileRequest({ fileId: id, fileName: name, file: this.file!, uploadUrl: url, formFields, }), ); } private async publishFileMessage(fileId: string, fileName: string): Promise<FileSharing.SendFileResponse> { let result: FileSharing.PublishFileMessageResponse = { timetoken: '0' }; let retries = this.parameters.fileUploadPublishRetryLimit; let publishError: PubNubError | undefined; let wasSuccessful = false; do { try { result = await this.parameters.publishFile({ ...this.parameters, fileId, fileName }); wasSuccessful = true; } catch (error: unknown) { if (error instanceof PubNubError) publishError = error; retries -= 1; } } while (!wasSuccessful && retries > 0); if (!wasSuccessful) { throw new PubNubError( 'Publish failed. You may want to execute that operation manually using pubnub.publishFile', { error: true, category: publishError!.status?.category ?? StatusCategory.PNUnknownCategory, statusCode: publishError!.status?.statusCode ?? 0, channel: this.parameters.channel, id: fileId, name: fileName, }, ); } else return { status: 200, timetoken: result.timetoken, id: fileId, name: fileName }; } }