cosmos-explorer/src/Explorer/Tables/TableDataClient.ts
2020-06-23 10:45:51 -05:00

747 lines
28 KiB
TypeScript

import * as ko from "knockout";
import Q from "q";
import { displayTokenRenewalPromptForStatus, getAuthorizationHeader } from "../../Utils/AuthorizationUtils";
import { AuthType } from "../../AuthType";
import { ConsoleDataType } from "../../Explorer/Menus/NotificationConsole/NotificationConsoleComponent";
import * as Constants from "../../Common/Constants";
import * as Entities from "./Entities";
import EnvironmentUtility from "../../Common/EnvironmentUtility";
import * as HeadersUtility from "../../Common/HeadersUtility";
import * as Logger from "../../Common/Logger";
import { NotificationConsoleUtils } from "../../Utils/NotificationConsoleUtils";
import * as TableConstants from "./Constants";
import * as TableEntityProcessor from "./TableEntityProcessor";
import * as ViewModels from "../../Contracts/ViewModels";
import { MessageTypes } from "../../Contracts/ExplorerContracts";
import { MessageHandler } from "../../Common/MessageHandler";
import DocumentClientUtilityBase from "../../Common/DocumentClientUtilityBase";
export interface CassandraTableKeys {
partitionKeys: CassandraTableKey[];
clusteringKeys: CassandraTableKey[];
}
export interface CassandraTableKey {
property: string;
type: string;
}
export abstract class TableDataClient {
public documentClientUtility: DocumentClientUtilityBase;
constructor(documentClientUtility: DocumentClientUtilityBase) {
this.documentClientUtility = documentClientUtility;
}
public abstract createDocument(
collection: ViewModels.Collection,
entity: Entities.ITableEntity
): Q.Promise<Entities.ITableEntity>;
public abstract updateDocument(
collection: ViewModels.Collection,
originalDocument: any,
newEntity: Entities.ITableEntity
): Q.Promise<Entities.ITableEntity>;
public abstract queryDocuments(
collection: ViewModels.Collection,
query: string,
shouldNotify?: boolean,
paginationToken?: string
): Q.Promise<Entities.IListTableEntitiesResult>;
public abstract deleteDocuments(
collection: ViewModels.Collection,
entitiesToDelete: Entities.ITableEntity[]
): Q.Promise<any>;
}
export class TablesAPIDataClient extends TableDataClient {
public createDocument(
collection: ViewModels.Collection,
entity: Entities.ITableEntity
): Q.Promise<Entities.ITableEntity> {
const deferred = Q.defer<Entities.ITableEntity>();
this.documentClientUtility
.createDocument(
collection,
TableEntityProcessor.convertEntityToNewDocument(<Entities.ITableEntityForTablesAPI>entity)
)
.then(
(newDocument: any) => {
const newEntity = TableEntityProcessor.convertDocumentsToEntities([newDocument])[0];
deferred.resolve(newEntity);
},
reason => {
deferred.reject(reason);
}
);
return deferred.promise;
}
public updateDocument(
collection: ViewModels.Collection,
originalDocument: any,
entity: Entities.ITableEntity
): Q.Promise<Entities.ITableEntity> {
const deferred = Q.defer<Entities.ITableEntity>();
this.documentClientUtility
.updateDocument(
collection,
originalDocument,
TableEntityProcessor.convertEntityToNewDocument(<Entities.ITableEntityForTablesAPI>entity)
)
.then(
(newDocument: any) => {
const newEntity = TableEntityProcessor.convertDocumentsToEntities([newDocument])[0];
deferred.resolve(newEntity);
},
reason => {
deferred.reject(reason);
}
);
return deferred.promise;
}
public queryDocuments(
collection: ViewModels.Collection,
query: string
): Q.Promise<Entities.IListTableEntitiesResult> {
const deferred = Q.defer<Entities.IListTableEntitiesResult>();
let options: any = {};
options.enableCrossPartitionQuery = HeadersUtility.shouldEnableCrossPartitionKey();
this.documentClientUtility.queryDocuments(collection.databaseId, collection.id(), query, options).then(
iterator => {
iterator
.fetchNext()
.then(response => response.resources)
.then(
(documents: any[] = []) => {
let entities: Entities.ITableEntity[] = TableEntityProcessor.convertDocumentsToEntities(documents);
let finalEntities: Entities.IListTableEntitiesResult = <Entities.IListTableEntitiesResult>{
Results: entities,
ContinuationToken: iterator.hasMoreResults(),
iterator: iterator
};
deferred.resolve(finalEntities);
},
reason => {
deferred.reject(reason);
}
);
},
reason => {
deferred.reject(reason);
}
);
return deferred.promise;
}
public deleteDocuments(collection: ViewModels.Collection, entitiesToDelete: Entities.ITableEntity[]): Q.Promise<any> {
let documentsToDelete: any[] = TableEntityProcessor.convertEntitiesToDocuments(
<Entities.ITableEntityForTablesAPI[]>entitiesToDelete,
collection
);
let promiseArray: Q.Promise<any>[] = [];
documentsToDelete &&
documentsToDelete.forEach(document => {
document.id = ko.observable<string>(document.id);
let promise: Q.Promise<any> = this.documentClientUtility.deleteDocument(collection, document);
promiseArray.push(promise);
});
return Q.all(promiseArray);
}
}
export class CassandraAPIDataClient extends TableDataClient {
public createDocument(
collection: ViewModels.Collection,
entity: Entities.ITableEntity
): Q.Promise<Entities.ITableEntity> {
const notificationId = NotificationConsoleUtils.logConsoleMessage(
ConsoleDataType.InProgress,
`Adding new row to table ${collection.id()}`
);
let properties = "(";
let values = "(";
for (let property in entity) {
if (entity[property]._ === null) {
continue;
}
properties = properties.concat(`${property}, `);
const propertyType = entity[property].$;
if (this.isStringType(propertyType)) {
values = values.concat(`'${entity[property]._}', `);
} else {
values = values.concat(`${entity[property]._}, `);
}
}
properties = properties.slice(0, properties.length - 2) + ")";
values = values.slice(0, values.length - 2) + ")";
const query = `INSERT INTO ${collection.databaseId}.${collection.id()} ${properties} VALUES ${values}`;
const deferred = Q.defer<Entities.ITableEntity>();
this.queryDocuments(collection, query)
.then(
(data: any) => {
entity[TableConstants.EntityKeyNames.RowKey] = entity[this.getCassandraPartitionKeyProperty(collection)];
entity[TableConstants.EntityKeyNames.RowKey]._ = entity[TableConstants.EntityKeyNames.RowKey]._.toString();
NotificationConsoleUtils.logConsoleMessage(
ConsoleDataType.Info,
`Successfully added new row to table ${collection.id()}`
);
deferred.resolve(entity);
},
reason => {
NotificationConsoleUtils.logConsoleMessage(
ConsoleDataType.Error,
`Error while adding new row to table ${collection.id()}:\n ${JSON.stringify(reason)}`
);
Logger.logError(JSON.stringify(reason), "AddRowCassandra", reason.code);
this._checkForbiddenError(reason);
deferred.reject(reason);
}
)
.finally(() => {
NotificationConsoleUtils.clearInProgressMessageWithId(notificationId);
});
return deferred.promise;
}
public updateDocument(
collection: ViewModels.Collection,
originalDocument: any,
newEntity: Entities.ITableEntity
): Q.Promise<Entities.ITableEntity> {
const notificationId = NotificationConsoleUtils.logConsoleMessage(
ConsoleDataType.InProgress,
`Updating row ${originalDocument.RowKey._}`
);
const deferred = Q.defer<Entities.ITableEntity>();
let promiseArray: Q.Promise<any>[] = [];
let query = `UPDATE ${collection.databaseId}.${collection.id()}`;
let isChange: boolean = false;
for (let property in newEntity) {
if (!originalDocument[property] || newEntity[property]._.toString() !== originalDocument[property]._.toString()) {
if (this.isStringType(newEntity[property].$)) {
query = `${query} SET ${property} = '${newEntity[property]._}',`;
} else {
query = `${query} SET ${property} = ${newEntity[property]._},`;
}
isChange = true;
}
}
query = query.slice(0, query.length - 1);
let whereSegment = " WHERE";
let keys: CassandraTableKey[] = collection.cassandraKeys.partitionKeys.concat(
collection.cassandraKeys.clusteringKeys
);
for (let keyIndex in keys) {
const key = keys[keyIndex].property;
const keyType = keys[keyIndex].type;
if (this.isStringType(keyType)) {
whereSegment = `${whereSegment} ${key} = '${newEntity[key]._}' AND`;
} else {
whereSegment = `${whereSegment} ${key} = ${newEntity[key]._} AND`;
}
}
whereSegment = whereSegment.slice(0, whereSegment.length - 4);
query = query + whereSegment;
if (isChange) {
promiseArray.push(this.queryDocuments(collection, query));
}
query = `DELETE `;
for (let property in originalDocument) {
if (property !== TableConstants.EntityKeyNames.RowKey && !newEntity[property] && !!originalDocument[property]) {
query = `${query} ${property},`;
}
}
if (query.length > 7) {
query = query.slice(0, query.length - 1);
query = `${query} FROM ${collection.databaseId}.${collection.id()}${whereSegment}`;
promiseArray.push(this.queryDocuments(collection, query));
}
Q.all(promiseArray)
.then(
(data: any) => {
newEntity[TableConstants.EntityKeyNames.RowKey] = originalDocument[TableConstants.EntityKeyNames.RowKey];
NotificationConsoleUtils.logConsoleMessage(
ConsoleDataType.Info,
`Successfully updated row ${newEntity.RowKey._}`
);
deferred.resolve(newEntity);
},
reason => {
NotificationConsoleUtils.logConsoleMessage(
ConsoleDataType.Error,
`Failed to update row ${newEntity.RowKey._}: ${JSON.stringify(reason)}`
);
Logger.logError(JSON.stringify(reason), "UpdateRowCassandra", reason.code);
this._checkForbiddenError(reason);
deferred.reject(reason);
}
)
.finally(() => {
NotificationConsoleUtils.clearInProgressMessageWithId(notificationId);
});
return deferred.promise;
}
public queryDocuments(
collection: ViewModels.Collection,
query: string,
shouldNotify?: boolean,
paginationToken?: string
): Q.Promise<Entities.IListTableEntitiesResult> {
let notificationId: string;
if (shouldNotify) {
notificationId = NotificationConsoleUtils.logConsoleMessage(
ConsoleDataType.InProgress,
`Querying rows for table ${collection.id()}`
);
}
const deferred = Q.defer<Entities.IListTableEntitiesResult>();
const authType = window.authType;
const apiEndpoint: string =
authType === AuthType.EncryptedToken
? Constants.CassandraBackend.guestQueryApi
: Constants.CassandraBackend.queryApi;
$.ajax(`${EnvironmentUtility.getCassandraBackendEndpoint(collection.container)}${apiEndpoint}`, {
type: "POST",
data: {
accountName: collection && collection.container.databaseAccount && collection.container.databaseAccount().name,
cassandraEndpoint: this.trimCassandraEndpoint(
collection.container.databaseAccount().properties.cassandraEndpoint
),
resourceId: collection.container.databaseAccount().id,
keyspaceId: collection.databaseId,
tableId: collection.id(),
query: query,
paginationToken: paginationToken
},
beforeSend: this.setAuthorizationHeader,
error: this.handleAjaxError,
cache: false
})
.then(
(data: any) => {
if (shouldNotify) {
NotificationConsoleUtils.logConsoleMessage(
ConsoleDataType.Info,
`Successfully fetched ${data.result.length} rows for table ${collection.id()}`
);
}
deferred.resolve({
Results: data.result,
ContinuationToken: data.paginationToken
});
},
reason => {
if (shouldNotify) {
NotificationConsoleUtils.logConsoleMessage(
ConsoleDataType.Error,
`Failed to query rows for table ${collection.id()}: ${JSON.stringify(reason)}`
);
Logger.logError(JSON.stringify(reason), "QueryDocumentsCassandra", reason.status);
this._checkForbiddenError(reason);
}
deferred.reject(reason);
}
)
.done(() => {
if (shouldNotify) {
NotificationConsoleUtils.clearInProgressMessageWithId(notificationId);
}
});
return deferred.promise;
}
public deleteDocuments(collection: ViewModels.Collection, entitiesToDelete: Entities.ITableEntity[]): Q.Promise<any> {
const query = `DELETE FROM ${collection.databaseId}.${collection.id()} WHERE `;
let promiseArray: Q.Promise<any>[] = [];
let partitionKeyProperty = this.getCassandraPartitionKeyProperty(collection);
for (let i = 0, len = entitiesToDelete.length; i < len; i++) {
let currEntityToDelete: Entities.ITableEntity = entitiesToDelete[i];
let currQuery = query;
let partitionKeyValue = currEntityToDelete[partitionKeyProperty];
if (partitionKeyValue._ != null && this.isStringType(partitionKeyValue.$)) {
currQuery = `${currQuery}${partitionKeyProperty} = '${partitionKeyValue._}' AND `;
} else {
currQuery = `${currQuery}${partitionKeyProperty} = ${partitionKeyValue._} AND `;
}
currQuery = currQuery.slice(0, currQuery.length - 5);
const notificationId = NotificationConsoleUtils.logConsoleMessage(
ConsoleDataType.InProgress,
`Deleting row ${currEntityToDelete.RowKey._}`
);
promiseArray.push(
this.queryDocuments(collection, currQuery)
.then(
() => {
NotificationConsoleUtils.logConsoleMessage(
ConsoleDataType.Info,
`Successfully deleted row ${currEntityToDelete.RowKey._}`
);
},
reason => {
NotificationConsoleUtils.logConsoleMessage(
ConsoleDataType.Error,
`Error while deleting row ${currEntityToDelete.RowKey._}:\n ${JSON.stringify(reason)}`
);
Logger.logError(JSON.stringify(reason), "DeleteRowCassandra", reason.code);
this._checkForbiddenError(reason);
}
)
.finally(() => {
NotificationConsoleUtils.clearInProgressMessageWithId(notificationId);
})
);
}
return Q.all(promiseArray);
}
public createKeyspace(
cassandraEndpoint: string,
resourceId: string,
explorer: ViewModels.Explorer,
createKeyspaceQuery: string
): Q.Promise<any> {
if (!createKeyspaceQuery) {
return Q.reject("No query specified");
}
const deferred: Q.Deferred<any> = Q.defer();
const notificationId = NotificationConsoleUtils.logConsoleMessage(
ConsoleDataType.InProgress,
`Creating a new keyspace with query ${createKeyspaceQuery}`
);
this.createOrDeleteQuery(cassandraEndpoint, resourceId, createKeyspaceQuery, explorer)
.then(
(data: any) => {
NotificationConsoleUtils.logConsoleMessage(
ConsoleDataType.Info,
`Successfully created a keyspace with query ${createKeyspaceQuery}`
);
explorer.documentClientUtility.refreshCachedResources().finally(() => deferred.resolve());
},
reason => {
NotificationConsoleUtils.logConsoleMessage(
ConsoleDataType.Error,
`Error while creating a keyspace with query ${createKeyspaceQuery}:\n ${JSON.stringify(reason)}`
);
Logger.logError(JSON.stringify(reason), "CreateKeyspaceCassandra", reason.code);
this._checkForbiddenError(reason);
deferred.reject(reason);
}
)
.finally(() => {
NotificationConsoleUtils.clearInProgressMessageWithId(notificationId);
});
return deferred.promise.timeout(Constants.ClientDefaults.requestTimeoutMs);
}
public createTableAndKeyspace(
cassandraEndpoint: string,
resourceId: string,
explorer: ViewModels.Explorer,
createTableQuery: string,
createKeyspaceQuery?: string
): Q.Promise<any> {
let createKeyspacePromise: Q.Promise<any>;
if (createKeyspaceQuery) {
createKeyspacePromise = this.createKeyspace(cassandraEndpoint, resourceId, explorer, createKeyspaceQuery);
} else {
createKeyspacePromise = Q.resolve(null);
}
const deferred = Q.defer();
createKeyspacePromise.then(
() => {
const notificationId = NotificationConsoleUtils.logConsoleMessage(
ConsoleDataType.InProgress,
`Creating a new table with query ${createTableQuery}`
);
this.createOrDeleteQuery(cassandraEndpoint, resourceId, createTableQuery, explorer)
.then(
(data: any) => {
NotificationConsoleUtils.logConsoleMessage(
ConsoleDataType.Info,
`Successfully created a table with query ${createTableQuery}`
);
this.documentClientUtility.refreshCachedResources(null).then(
() => {
deferred.resolve();
},
reason => {
// Still resolve since the keyspace/table was successfully created at this point.
deferred.resolve();
}
);
},
reason => {
NotificationConsoleUtils.logConsoleMessage(
ConsoleDataType.Error,
`Error while creating a table with query ${createTableQuery}:\n ${JSON.stringify(reason)}`
);
Logger.logError(JSON.stringify(reason), "CreateTableCassandra", reason.code);
this._checkForbiddenError(reason);
deferred.reject(reason);
}
)
.finally(() => {
NotificationConsoleUtils.clearInProgressMessageWithId(notificationId);
});
},
reason => {
deferred.reject(reason);
}
);
return deferred.promise;
}
public deleteTableOrKeyspace(
cassandraEndpoint: string,
resourceId: string,
deleteQuery: string,
explorer: ViewModels.Explorer
): Q.Promise<any> {
const deferred = Q.defer<any>();
const notificationId = NotificationConsoleUtils.logConsoleMessage(
ConsoleDataType.InProgress,
`Deleting resource with query ${deleteQuery}`
);
this.createOrDeleteQuery(cassandraEndpoint, resourceId, deleteQuery, explorer)
.then(
() => {
NotificationConsoleUtils.logConsoleMessage(
ConsoleDataType.Info,
`Successfully deleted resource with query ${deleteQuery}`
);
this.documentClientUtility.refreshCachedResources(null).then(
() => {
deferred.resolve();
},
reason => {
// Still resolve since the keyspace/table was successfully deleted at this point.
deferred.resolve();
}
);
},
reason => {
NotificationConsoleUtils.logConsoleMessage(
ConsoleDataType.Error,
`Error while deleting resource with query ${deleteQuery}:\n ${JSON.stringify(reason)}`
);
Logger.logError(JSON.stringify(reason), "DeleteKeyspaceOrTableCassandra", reason.code);
this._checkForbiddenError(reason);
deferred.reject(reason);
}
)
.finally(() => {
NotificationConsoleUtils.clearInProgressMessageWithId(notificationId);
});
return deferred.promise;
}
public getTableKeys(collection: ViewModels.Collection): Q.Promise<CassandraTableKeys> {
if (!!collection.cassandraKeys) {
return Q.resolve(collection.cassandraKeys);
}
const notificationId = NotificationConsoleUtils.logConsoleMessage(
ConsoleDataType.InProgress,
`Fetching keys for table ${collection.id()}`
);
const authType = window.authType;
const apiEndpoint: string =
authType === AuthType.EncryptedToken
? Constants.CassandraBackend.guestKeysApi
: Constants.CassandraBackend.keysApi;
let endpoint = `${EnvironmentUtility.getCassandraBackendEndpoint(collection.container)}${apiEndpoint}`;
const deferred = Q.defer<CassandraTableKeys>();
$.ajax(endpoint, {
type: "POST",
data: {
accountName: collection && collection.container.databaseAccount && collection.container.databaseAccount().name,
cassandraEndpoint: this.trimCassandraEndpoint(
collection.container.databaseAccount().properties.cassandraEndpoint
),
resourceId: collection.container.databaseAccount().id,
keyspaceId: collection.databaseId,
tableId: collection.id()
},
beforeSend: this.setAuthorizationHeader,
error: this.handleAjaxError,
cache: false
})
.then(
(data: CassandraTableKeys) => {
collection.cassandraKeys = data;
NotificationConsoleUtils.logConsoleMessage(
ConsoleDataType.Info,
`Successfully fetched keys for table ${collection.id()}`
);
deferred.resolve(data);
},
reason => {
NotificationConsoleUtils.logConsoleMessage(
ConsoleDataType.Error,
`Error fetching keys for table ${collection.id()}:\n ${JSON.stringify(reason)}`
);
Logger.logError(JSON.stringify(reason), "FetchKeysCassandra", reason.status);
this._checkForbiddenError(reason);
deferred.reject(reason);
}
)
.done(() => {
NotificationConsoleUtils.clearInProgressMessageWithId(notificationId);
});
return deferred.promise;
}
public getTableSchema(collection: ViewModels.Collection): Q.Promise<CassandraTableKey[]> {
if (!!collection.cassandraSchema) {
return Q.resolve(collection.cassandraSchema);
}
const notificationId = NotificationConsoleUtils.logConsoleMessage(
ConsoleDataType.InProgress,
`Fetching schema for table ${collection.id()}`
);
const authType = window.authType;
const apiEndpoint: string =
authType === AuthType.EncryptedToken
? Constants.CassandraBackend.guestSchemaApi
: Constants.CassandraBackend.schemaApi;
let endpoint = `${EnvironmentUtility.getCassandraBackendEndpoint(collection.container)}${apiEndpoint}`;
const deferred = Q.defer<CassandraTableKey[]>();
$.ajax(endpoint, {
type: "POST",
data: {
accountName: collection && collection.container.databaseAccount && collection.container.databaseAccount().name,
cassandraEndpoint: this.trimCassandraEndpoint(
collection.container.databaseAccount().properties.cassandraEndpoint
),
resourceId: collection.container.databaseAccount().id,
keyspaceId: collection.databaseId,
tableId: collection.id()
},
beforeSend: this.setAuthorizationHeader,
error: this.handleAjaxError,
cache: false
})
.then(
(data: any) => {
collection.cassandraSchema = data.columns;
NotificationConsoleUtils.logConsoleMessage(
ConsoleDataType.Info,
`Successfully fetched schema for table ${collection.id()}`
);
deferred.resolve(data.columns);
},
reason => {
NotificationConsoleUtils.logConsoleMessage(
ConsoleDataType.Error,
`Error fetching schema for table ${collection.id()}:\n ${JSON.stringify(reason)}`
);
Logger.logError(JSON.stringify(reason), "FetchSchemaCassandra", reason.status);
this._checkForbiddenError(reason);
deferred.reject(reason);
}
)
.done(() => {
NotificationConsoleUtils.clearInProgressMessageWithId(notificationId);
});
return deferred.promise;
}
private createOrDeleteQuery(
cassandraEndpoint: string,
resourceId: string,
query: string,
explorer: ViewModels.Explorer
): Q.Promise<any> {
const deferred = Q.defer();
const authType = window.authType;
const apiEndpoint: string =
authType === AuthType.EncryptedToken
? Constants.CassandraBackend.guestCreateOrDeleteApi
: Constants.CassandraBackend.createOrDeleteApi;
$.ajax(`${EnvironmentUtility.getCassandraBackendEndpoint(explorer)}${apiEndpoint}`, {
type: "POST",
data: {
accountName: explorer.databaseAccount() && explorer.databaseAccount().name,
cassandraEndpoint: this.trimCassandraEndpoint(cassandraEndpoint),
resourceId: resourceId,
query: query
},
beforeSend: this.setAuthorizationHeader,
error: this.handleAjaxError,
cache: false
}).then(
(data: any) => {
deferred.resolve();
},
reason => {
deferred.reject(reason);
}
);
return deferred.promise;
}
private trimCassandraEndpoint(cassandraEndpoint: string): string {
if (!cassandraEndpoint) {
return cassandraEndpoint;
}
if (cassandraEndpoint.indexOf("https://") === 0) {
cassandraEndpoint = cassandraEndpoint.slice(8, cassandraEndpoint.length);
}
if (cassandraEndpoint.indexOf(":443/", cassandraEndpoint.length - 5) !== -1) {
cassandraEndpoint = cassandraEndpoint.slice(0, cassandraEndpoint.length - 5);
}
return cassandraEndpoint;
}
private setAuthorizationHeader: (xhr: XMLHttpRequest) => boolean = (xhr: XMLHttpRequest): boolean => {
const authorizationHeaderMetadata: ViewModels.AuthorizationTokenHeaderMetadata = getAuthorizationHeader();
xhr.setRequestHeader(authorizationHeaderMetadata.header, authorizationHeaderMetadata.token);
return true;
};
private isStringType(dataType: string): boolean {
// TODO figure out rest of types that are considered strings by Cassandra (if any have been missed)
return (
dataType === TableConstants.CassandraType.Text ||
dataType === TableConstants.CassandraType.Inet ||
dataType === TableConstants.CassandraType.Ascii ||
dataType === TableConstants.CassandraType.Varchar
);
}
private getCassandraPartitionKeyProperty(collection: ViewModels.Collection): string {
return collection.cassandraKeys.partitionKeys[0].property;
}
private handleAjaxError = (xhrObj: XMLHttpRequest, textStatus: string, errorThrown: string): void => {
if (!xhrObj) {
return;
}
displayTokenRenewalPromptForStatus(xhrObj.status);
};
private _checkForbiddenError(reason: any) {
if (reason && reason.code === Constants.HttpStatusCodes.Forbidden) {
MessageHandler.sendMessage({
type: MessageTypes.ForbiddenError,
reason: typeof reason === "string" ? "reason" : JSON.stringify(reason)
});
}
}
}