Skip to content

Commit

Permalink
types(ajax): improve Ajax types and docs (#6233)
Browse files Browse the repository at this point in the history
* docs(ajax): improve Ajax docs

* chore: update api_guardian
  • Loading branch information
jakovljevic-mladen authored Apr 19, 2021
1 parent 14cc685 commit 40206e5
Show file tree
Hide file tree
Showing 6 changed files with 56 additions and 36 deletions.
2 changes: 2 additions & 0 deletions api_guard/dist/types/ajax/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ export interface AjaxConfig {
xsrfHeaderName?: string;
}

export declare type AjaxDirection = 'upload' | 'download';

export interface AjaxError extends Error {
request: AjaxRequest;
response: any;
Expand Down
2 changes: 1 addition & 1 deletion src/ajax/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export { ajax } from '../internal/ajax/ajax';
export { AjaxError, AjaxTimeoutError } from '../internal/ajax/errors';
export { AjaxResponse } from '../internal/ajax/AjaxResponse';
export { AjaxRequest, AjaxConfig } from '../internal/ajax/types';
export { AjaxRequest, AjaxConfig, AjaxDirection } from '../internal/ajax/types';
22 changes: 15 additions & 7 deletions src/internal/ajax/AjaxResponse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { getXHRResponse } from './getXHRResponse';
* request and response data.
*
* @see {@link ajax}
* @see {@link AjaxConfig}
*/
export class AjaxResponse<T> {
/** The HTTP status code */
Expand All @@ -32,17 +33,15 @@ export class AjaxResponse<T> {

/**
* The total number of bytes loaded so far. To be used with {@link total} while
* calcating progress. (You will want to set {@link includeDownloadProgress} or {@link includeDownloadProgress})
*
* {@see {@link AjaxConfig}}
* calculating progress. (You will want to set {@link includeDownloadProgress} or
* {@link includeDownloadProgress})
*/
readonly loaded: number;

/**
* The total number of bytes to be loaded. To be used with {@link loaded} while
* calcating progress. (You will want to set {@link includeDownloadProgress} or {@link includeDownloadProgress})
*
* {@see {@link AjaxConfig}}
* calculating progress. (You will want to set {@link includeDownloadProgress} or
* {@link includeDownloadProgress})
*/
readonly total: number;

Expand All @@ -61,6 +60,7 @@ export class AjaxResponse<T> {
* @param originalEvent The original event object from the XHR `onload` event.
* @param xhr The `XMLHttpRequest` object used to make the request. This is useful for examining status code, etc.
* @param request The request settings used to make the HTTP request.
* @param type The type of the event emitted by the {@link ajax} Observable
*/
constructor(
/**
Expand All @@ -81,8 +81,16 @@ export class AjaxResponse<T> {
* The event type. This can be used to discern between different events
* if you're using progress events with {@link includeDownloadProgress} or
* {@link includeUploadProgress} settings in {@link AjaxConfig}.
*
* The event type consists of two parts: the {@link AjaxDirection} and the
* the event type. Merged with `_`, they form the `type` string. The
* direction can be an `upload` or a `download` direction, while an event can
* be `loadstart`, `progress` or `load`.
*
* `download_load` is the type of event when download has finished and the
* response is available.
*/
public readonly type = 'download_load'
public readonly type: string = 'download_load'
) {
const { status, responseType } = xhr;
this.status = status ?? 0;
Expand Down
33 changes: 17 additions & 16 deletions src/internal/ajax/ajax.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { map } from '../operators/map';
import { Observable } from '../Observable';
import { AjaxConfig, AjaxRequest } from './types';
import { AjaxConfig, AjaxRequest, AjaxDirection } from './types';
import { AjaxResponse } from './AjaxResponse';
import { AjaxTimeoutError, AjaxError } from './errors';

Expand Down Expand Up @@ -248,14 +248,14 @@ function ajaxGetJSON<T>(url: string, headers?: Record<string, string>): Observab
* ```
*/
export const ajax: AjaxCreationMethod = (() => {
const create = <T>(urlOrRequest: string | AjaxConfig) => {
const request =
typeof urlOrRequest === 'string'
const create = <T>(urlOrConfig: string | AjaxConfig) => {
const config: AjaxConfig =
typeof urlOrConfig === 'string'
? {
url: urlOrRequest,
url: urlOrConfig,
}
: urlOrRequest;
return fromAjax<T>(request);
: urlOrConfig;
return fromAjax<T>(config);
};

create.get = ajaxGet;
Expand All @@ -268,6 +268,7 @@ export const ajax: AjaxCreationMethod = (() => {
return create;
})();

const UPLOAD = 'upload';
const DOWNLOAD = 'download';
const LOADSTART = 'loadstart';
const PROGRESS = 'progress';
Expand Down Expand Up @@ -315,7 +316,7 @@ export function fromAjax<T>(config: AjaxConfig): Observable<AjaxResponse<T>> {
}

// Normalize the headers. We're going to make them all lowercase, since
// Headers are case insenstive by design. This makes it easier to verify
// Headers are case insensitive by design. This makes it easier to verify
// that we aren't setting or sending duplicates.
const headers: Record<string, any> = {};
if (configuredHeaders) {
Expand All @@ -327,7 +328,7 @@ export function fromAjax<T>(config: AjaxConfig): Observable<AjaxResponse<T>> {
}

// Set the x-requested-with header. This is a non-standard header that has
// come to be a defacto standard for HTTP requests sent by libraries and frameworks
// come to be a de facto standard for HTTP requests sent by libraries and frameworks
// using XHR. However, we DO NOT want to set this if it is a CORS request. This is
// because sometimes this header can cause issues with CORS. To be clear,
// None of this is necessary, it's only being set because it's "the thing libraries do"
Expand Down Expand Up @@ -407,12 +408,12 @@ export function fromAjax<T>(config: AjaxConfig): Observable<AjaxResponse<T>> {

/**
* Creates a response object to emit to the consumer.
* @param direction the direction related to the event. Prefixes the event `type` in the response
* object. So "upload_" for events related to uploading, and "download_" for events related to
* downloading.
* @param direction the direction related to the event. Prefixes the event `type` in the
* `AjaxResponse` object with "upload_" for events related to uploading and "download_"
* for events related to downloading.
* @param event the actual event object.
*/
const createResponse = (direction: 'upload' | 'download', event: ProgressEvent) =>
const createResponse = (direction: AjaxDirection, event: ProgressEvent) =>
new AjaxResponse<T>(event, xhr, _request, `${direction}_${event.type}`);

/**
Expand All @@ -425,14 +426,14 @@ export function fromAjax<T>(config: AjaxConfig): Observable<AjaxResponse<T>> {
* @param direction The "direction", used to prefix the response object that is
* emitted to the consumer. (e.g. "upload_" or "download_")
*/
const addProgressEvent = (target: any, type: string, direction: 'upload' | 'download') => {
const addProgressEvent = (target: any, type: string, direction: AjaxDirection) => {
target.addEventListener(type, (event: ProgressEvent) => {
destination.next(createResponse(direction, event));
});
};

if (includeUploadProgress) {
[LOADSTART, PROGRESS, LOAD].forEach((type) => addProgressEvent(xhr.upload, type, 'upload'));
[LOADSTART, PROGRESS, LOAD].forEach((type) => addProgressEvent(xhr.upload, type, UPLOAD));
}

if (progressSubscriber) {
Expand Down Expand Up @@ -554,7 +555,7 @@ function extractContentTypeAndMaybeSerializeBody(body: any, headers: Record<stri
// If we have made it here, this is an object, probably a POJO, and we'll try
// to serialize it for them. If this doesn't work, it will throw, obviously, which
// is okay. The workaround for users would be to manually set the body to their own
// serialized string (accounting for circular refrences or whatever), then set
// serialized string (accounting for circular references or whatever), then set
// the content-type manually as well.
headers['content-type'] = headers['content-type'] ?? 'application/json;charset=utf-8';
return JSON.stringify(body);
Expand Down
13 changes: 7 additions & 6 deletions src/internal/ajax/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,27 +11,28 @@ import { createErrorClass } from '../util/createErrorClass';
*/
export interface AjaxError extends Error {
/**
* The XHR instance associated with the error
* The XHR instance associated with the error.
*/
xhr: XMLHttpRequest;

/**
* The AjaxRequest associated with the error
* The AjaxRequest associated with the error.
*/
request: AjaxRequest;

/**
*The HTTP status code
* The HTTP status code, if the request has completed. If not,
* it is set to `0`.
*/
status: number;

/**
*The responseType (e.g. 'json', 'arraybuffer', or 'xml')
* The responseType (e.g. 'json', 'arraybuffer', or 'xml').
*/
responseType: XMLHttpRequestResponseType;

/**
* The response data
* The response data.
*/
response: any;
}
Expand Down Expand Up @@ -85,7 +86,7 @@ export interface AjaxTimeoutErrorCtor {
}

/**
* Thrown when an AJAX request timesout. Not to be confused with {@link TimeoutError}.
* Thrown when an AJAX request times out. Not to be confused with {@link TimeoutError}.
*
* This is exported only because it is useful for checking to see if errors are an
* `instanceof AjaxTimeoutError`. DO NOT use the constructor to create an instance of
Expand Down
20 changes: 14 additions & 6 deletions src/internal/ajax/types.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,17 @@
import { PartialObserver } from '../types';

/**
* Valid Ajax direction types. Prefixes the event `type` in the
* {@link AjaxResponse} object with "upload_" for events related
* to uploading and "download_" for events related to downloading.
*/
export type AjaxDirection = 'upload' | 'download';

/**
* The object containing values RxJS used to make the HTTP request.
*
* This is provided in {@link AjaxError} instances and
* This is provided in {@link AjaxError} instances as the `request`
* object.
*/
export interface AjaxRequest {
/**
Expand Down Expand Up @@ -74,7 +82,7 @@ export interface AjaxConfig {
/**
* The body of the HTTP request to send.
*
* This is serialized, by default, based off of the valud of the `"content-type"` header.
* This is serialized, by default, based off of the value of the `"content-type"` header.
* For example, if the `"content-type"` is `"application/json"`, the body will be serialized
* as JSON. If the `"content-type"` is `"application/x-www-form-urlencoded"`, whatever object passed
* to the body will be serialized as URL, using key-value pairs based off of the keys and values of the object.
Expand Down Expand Up @@ -179,17 +187,17 @@ export interface AjaxConfig {
progressSubscriber?: PartialObserver<ProgressEvent>;

/**
* If `true`, will emit all download progress and load complete events as {@link AjaxProgressEvents}
* from the observable. The final download event will also be emitted as a {@link AjaxDownloadCompleteEvent}
* If `true`, will emit all download progress and load complete events as {@link AjaxResponse}
* from the observable. The final download event will also be emitted as a {@link AjaxResponse}.
*
* If both this and {@link includeUploadProgress} are `false`, then only the {@link AjaxResponse} will
* be emitted from the resulting observable.
*/
includeDownloadProgress?: boolean;

/**
* If `true`, will emit all upload progress and load complete events as {@link AjaxUploadProgressEvents}
* from the observable. The final download event will also be emitted as a {@link AjaxDownloadCompleteEvent}
* If `true`, will emit all upload progress and load complete events as {@link AjaxResponse}
* from the observable. The final download event will also be emitted as a {@link AjaxResponse}.
*
* If both this and {@link includeDownloadProgress} are `false`, then only the {@link AjaxResponse} will
* be emitted from the resulting observable.
Expand Down

0 comments on commit 40206e5

Please sign in to comment.