pubnub
Version:
Publish & Subscribe Real-time Messaging with PubNub
206 lines (181 loc) • 6.4 kB
text/typescript
/**
* 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 };
}
}