Skip to content

Commit

Permalink
v1.0.0
Browse files Browse the repository at this point in the history
  • Loading branch information
stackptr committed Mar 10, 2023
1 parent e6a4bd9 commit bb2c8d2
Show file tree
Hide file tree
Showing 21 changed files with 935 additions and 57 deletions.
23 changes: 11 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,20 +1,19 @@
# npm Package Template
# @freckle/ajax

Our custom template repository for creating a package published to npm.
## Install

[Creating a repository from a template][docs].
```sh
yarn add @freckle/ajax
```

[docs]: https://docs.github.com/en/repositories/creating-and-managing-repositories/creating-a-repository-from-a-template
## Ajax helpers

**NOTE**: Be sure to look for strings like "TODO", "Package name", or "package-name" and update
them accordingly.
See [ajax.ts](./src/ajax.ts).

## Install
## Link header

```sh
yarn add package-name
```
See [link-header.ts](./src/link-header.ts).

## process(input)
---

TODO: Document public API for package.
[LICENSE](./LICENSE)
79 changes: 79 additions & 0 deletions dist/ajax.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
type MethodT = 'POST' | 'GET' | 'PATCH' | 'PUT' | 'HEAD' | 'DELETE';
type ContentTypeT = 'application/json; charset=utf-8' | 'application/x-www-form-urlencoded' | 'text/plain' | 'text/csv';
type DataTypeT = 'json' | 'text';
type AjaxCallOptionsT = {
url: string;
method: MethodT;
data?: any;
contentType?: ContentTypeT | null;
dataType: DataTypeT;
cache?: boolean;
xhrFields?: {
withCredentials: boolean;
};
timeout?: number;
};
export declare function ajaxCall<T>(options: AjaxCallOptionsT): Promise<T>;
type MethodWithStringifiedDataT = 'POST' | 'PATCH' | 'PUT' | 'HEAD' | 'DELETE';
type MethodWithRawDataT = 'GET';
export type AjaxJsonCallOptionsT = {
url: string;
method: MethodWithStringifiedDataT;
data?: string;
cache?: boolean;
xhrFields?: {
withCredentials: boolean;
};
timeout?: number;
} | {
url: string;
method: MethodWithRawDataT;
data?: any;
cache?: boolean;
xhrFields?: {
withCredentials: boolean;
};
timeout?: number;
};
export declare function ajaxJsonCall<T>(options: AjaxJsonCallOptionsT): Promise<T>;
type AjaxFormCallOptionsT = {
url: string;
method: MethodT;
data?: any;
};
export declare function ajaxFormCall<T>(options: AjaxFormCallOptionsT): Promise<T>;
type AjaxFormFileUploadOptionsT = {
url: string;
data: any;
method?: MethodT;
timeout?: number;
};
export declare function ajaxFormFileUpload<T>(options: AjaxFormFileUploadOptionsT): Promise<T>;
export type AjaxFileDownloadOptionsT = {
url: string;
accept: ContentTypeT;
defaultFilename: string;
};
export declare function ajaxFileDownload(options: AjaxFileDownloadOptionsT): Promise<void>;
type SendBeaconOptionsT = Inexact<{
url: string;
data: Inexact<{
[x: string]: any;
}>;
}>;
export declare function sendBeacon(options: SendBeaconOptionsT): void;
export declare function checkUrlExistence(url: string): Promise<boolean>;
/**
* This hack gets around a Chrome caching bug wherein the audio request generated
* by the audio web api leads to chrome caching a response that does not contain
* the appropriate CORS headers. Any subsequent testing of that resource by this method
* would then attempt to fetch the resource from cache and would error out with a missing
* access-control-allow-origin error. By appending the path name to the audioPath
* here, but not for the audio web api, we create a separate cache for this
* resource request and bypass using the cached response with the missing
* CORS Headers. This is reproducible in Chrome only.
*
* Root cause: https://bugs.chromium.org/p/chromium/issues/detail?id=260239
*/
export declare function appendParamToRemedyCorsBug(path: string): string;
export {};
147 changes: 147 additions & 0 deletions dist/ajax.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.appendParamToRemedyCorsBug = exports.checkUrlExistence = exports.sendBeacon = exports.ajaxFileDownload = exports.ajaxFormFileUpload = exports.ajaxFormCall = exports.ajaxJsonCall = exports.ajaxCall = void 0;
const maybe_1 = require("@freckle/maybe");
function ajaxCall(options) {
const { url, method, data, contentType, dataType, cache, xhrFields, timeout } = options;
const contentTypeHeader = contentType !== null && contentType !== undefined ? { contentType } : {};
const timeoutParam = timeout !== null && timeout !== undefined ? { timeout } : {};
return new Promise((resolve, reject) => {
$.ajax(Object.assign(Object.assign({ url, type: method, data,
dataType,
cache,
xhrFields }, timeoutParam), contentTypeHeader))
.then(resolve)
.fail(reject);
});
}
exports.ajaxCall = ajaxCall;
function ajaxJsonCall(options) {
const { url, method, data, cache, xhrFields, timeout } = options;
// If we are not sending any data along with the request then there is no need to specify the contentType
// For cross-domain requests, setting the content type to anything other than application/x-www-form-urlencoded,
// multipart/form-data, or text/plain will trigger the browser to send a preflight OPTIONS request to the server.
// We don't want to make a preflight request where it has no use.
const contentType = data !== null && data !== undefined ? 'application/json; charset=utf-8' : null;
const dataType = 'json';
return ajaxCall({ url, method, data, contentType, dataType, cache, xhrFields, timeout });
}
exports.ajaxJsonCall = ajaxJsonCall;
function ajaxFormCall(options) {
const { url, method, data } = options;
const contentType = 'application/x-www-form-urlencoded';
const dataType = 'json';
const cache = false;
return ajaxCall({ url, method, data, contentType, dataType, cache });
}
exports.ajaxFormCall = ajaxFormCall;
function ajaxFormFileUpload(options) {
const { url, data, method, timeout } = options;
const timeoutParam = timeout !== null && timeout !== undefined ? { timeout } : {};
return new Promise((resolve, reject) => {
$.ajax(Object.assign({ url, type: method ? method : 'POST', data, contentType: false, processData: false }, timeoutParam))
.then(resolve)
.fail(reject);
});
}
exports.ajaxFormFileUpload = ajaxFormFileUpload;
function ajaxFileDownload(options) {
const { url, accept, defaultFilename } = options;
return new Promise((resolve, reject) => {
const request = new XMLHttpRequest();
request.open('GET', url, true);
request.withCredentials = true;
request.responseType = 'blob';
request.setRequestHeader('Accept', accept);
// Reject on error
request.onerror = () => {
reject({
status: request.status,
statusText: request.statusText
});
};
// Create an anchor that downloads Blob using FileReader
request.onload = () => {
var _a;
if (request.status >= 200 && request.status < 300) {
const contentType = request.getResponseHeader('Content-Type');
const disposition = request.getResponseHeader('Content-Disposition');
const blob = new Blob([(_a = request.response) !== null && _a !== void 0 ? _a : ''], { type: contentType !== null && contentType !== void 0 ? contentType : undefined });
const reader = new FileReader();
reader.onload = e => {
const anchor = document.createElement('a');
anchor.style.display = 'none';
const target = e.target;
if (target instanceof FileReader && typeof target.result === 'string') {
anchor.href = target.result;
anchor.download = (0, maybe_1.fromMaybe)(() => defaultFilename, contentDispositionFilename(disposition));
anchor.click();
resolve();
}
else {
reject({
status: request.status,
statusText: request.statusText
});
}
};
reader.readAsDataURL(blob);
}
else {
reject({
status: request.status,
statusText: request.statusText
});
}
};
// Go
request.send();
});
}
exports.ajaxFileDownload = ajaxFileDownload;
function contentDispositionFilename(mDisposition) {
return (0, maybe_1.mthen)(mDisposition, disposition => (0, maybe_1.mthen)(disposition.trim().match(/attachment; filename="(.*)"/), ([_ignore, filename]) => filename));
}
function sendBeacon(options) {
const { url, data } = options;
try {
const jsonData = JSON.stringify(data);
window.navigator.sendBeacon(url, jsonData);
// eslint-disable-next-line no-empty
}
catch (e) { }
}
exports.sendBeacon = sendBeacon;
function checkUrlExistence(url) {
return new Promise(resolve => {
ajaxCall({
url,
method: 'HEAD',
contentType: 'text/plain',
dataType: 'text'
})
.then(() => {
resolve(true);
})
.catch(() => {
resolve(false);
});
});
}
exports.checkUrlExistence = checkUrlExistence;
/**
* This hack gets around a Chrome caching bug wherein the audio request generated
* by the audio web api leads to chrome caching a response that does not contain
* the appropriate CORS headers. Any subsequent testing of that resource by this method
* would then attempt to fetch the resource from cache and would error out with a missing
* access-control-allow-origin error. By appending the path name to the audioPath
* here, but not for the audio web api, we create a separate cache for this
* resource request and bypass using the cached response with the missing
* CORS Headers. This is reproducible in Chrome only.
*
* Root cause: https://bugs.chromium.org/p/chromium/issues/detail?id=260239
*/
function appendParamToRemedyCorsBug(path) {
return path.includes('?') ? `${path}&via=xmlHttpRequest` : `${path}?via=xmlHttpRequest`;
}
exports.appendParamToRemedyCorsBug = appendParamToRemedyCorsBug;
99 changes: 99 additions & 0 deletions dist/ajax.js.flow
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
// @flow
declare type MethodT = "POST" | "GET" | "PATCH" | "PUT" | "HEAD" | "DELETE";
declare type ContentTypeT =
| "application/json; charset=utf-8"
| "application/x-www-form-urlencoded"
| "text/plain"
| "text/csv";
declare type DataTypeT = "json" | "text";
declare type AjaxCallOptionsT = {|
url: string,
method: MethodT,
data?: any,
contentType?: ContentTypeT | null,
dataType: DataTypeT,
cache?: boolean,
xhrFields?: {|
withCredentials: boolean,
|},
timeout?: number,
|};
declare export function ajaxCall<T>(options: AjaxCallOptionsT): Promise<T>;
declare type MethodWithStringifiedDataT =
| "POST"
| "PATCH"
| "PUT"
| "HEAD"
| "DELETE";
declare type MethodWithRawDataT = "GET";
export type AjaxJsonCallOptionsT =
| {|
url: string,
method: MethodWithStringifiedDataT,
data?: string,
cache?: boolean,
xhrFields?: {|
withCredentials: boolean,
|},
timeout?: number,
|}
| {|
url: string,
method: MethodWithRawDataT,
data?: any,
cache?: boolean,
xhrFields?: {|
withCredentials: boolean,
|},
timeout?: number,
|};
declare export function ajaxJsonCall<T>(
options: AjaxJsonCallOptionsT
): Promise<T>;
declare type AjaxFormCallOptionsT = {|
url: string,
method: MethodT,
data?: any,
|};
declare export function ajaxFormCall<T>(
options: AjaxFormCallOptionsT
): Promise<T>;
declare type AjaxFormFileUploadOptionsT = {|
url: string,
data: any,
method?: MethodT,
timeout?: number,
|};
declare export function ajaxFormFileUpload<T>(
options: AjaxFormFileUploadOptionsT
): Promise<T>;
export type AjaxFileDownloadOptionsT = {|
url: string,
accept: ContentTypeT,
defaultFilename: string,
|};
declare export function ajaxFileDownload(
options: AjaxFileDownloadOptionsT
): Promise<void>;
declare type SendBeaconOptionsT = Inexact<{|
url: string,
data: Inexact<{
[x: string]: any,
}>,
|}>;
declare export function sendBeacon(options: SendBeaconOptionsT): void;
declare export function checkUrlExistence(url: string): Promise<boolean>;

/**
* This hack gets around a Chrome caching bug wherein the audio request generated
* by the audio web api leads to chrome caching a response that does not contain
* the appropriate CORS headers. Any subsequent testing of that resource by this method
* would then attempt to fetch the resource from cache and would error out with a missing
* access-control-allow-origin error. By appending the path name to the audioPath
* here, but not for the audio web api, we create a separate cache for this
* resource request and bypass using the cached response with the missing
* CORS Headers. This is reproducible in Chrome only.
*
* Root cause: https://bugs.chromium.org/p/chromium/issues/detail?id=260239
*/
declare export function appendParamToRemedyCorsBug(path: string): string;
5 changes: 4 additions & 1 deletion dist/index.d.ts
Original file line number Diff line number Diff line change
@@ -1 +1,4 @@
export { process } from './process';
export { ajaxCall, ajaxJsonCall, ajaxFormCall, ajaxFormFileUpload, ajaxFileDownload, sendBeacon, checkUrlExistence, appendParamToRemedyCorsBug } from './ajax';
export type { AjaxJsonCallOptionsT, AjaxFileDownloadOptionsT } from './ajax';
export { fromString, toString, parseLinkHeader, fetchWithLinks } from './link-header';
export type { LinkName, LinkPathT, LinksT } from './link-header';
18 changes: 15 additions & 3 deletions dist/index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,17 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.process = void 0;
var process_1 = require("./process");
Object.defineProperty(exports, "process", { enumerable: true, get: function () { return process_1.process; } });
exports.fetchWithLinks = exports.parseLinkHeader = exports.toString = exports.fromString = exports.appendParamToRemedyCorsBug = exports.checkUrlExistence = exports.sendBeacon = exports.ajaxFileDownload = exports.ajaxFormFileUpload = exports.ajaxFormCall = exports.ajaxJsonCall = exports.ajaxCall = void 0;
var ajax_1 = require("./ajax");
Object.defineProperty(exports, "ajaxCall", { enumerable: true, get: function () { return ajax_1.ajaxCall; } });
Object.defineProperty(exports, "ajaxJsonCall", { enumerable: true, get: function () { return ajax_1.ajaxJsonCall; } });
Object.defineProperty(exports, "ajaxFormCall", { enumerable: true, get: function () { return ajax_1.ajaxFormCall; } });
Object.defineProperty(exports, "ajaxFormFileUpload", { enumerable: true, get: function () { return ajax_1.ajaxFormFileUpload; } });
Object.defineProperty(exports, "ajaxFileDownload", { enumerable: true, get: function () { return ajax_1.ajaxFileDownload; } });
Object.defineProperty(exports, "sendBeacon", { enumerable: true, get: function () { return ajax_1.sendBeacon; } });
Object.defineProperty(exports, "checkUrlExistence", { enumerable: true, get: function () { return ajax_1.checkUrlExistence; } });
Object.defineProperty(exports, "appendParamToRemedyCorsBug", { enumerable: true, get: function () { return ajax_1.appendParamToRemedyCorsBug; } });
var link_header_1 = require("./link-header");
Object.defineProperty(exports, "fromString", { enumerable: true, get: function () { return link_header_1.fromString; } });
Object.defineProperty(exports, "toString", { enumerable: true, get: function () { return link_header_1.toString; } });
Object.defineProperty(exports, "parseLinkHeader", { enumerable: true, get: function () { return link_header_1.parseLinkHeader; } });
Object.defineProperty(exports, "fetchWithLinks", { enumerable: true, get: function () { return link_header_1.fetchWithLinks; } });
Loading

0 comments on commit bb2c8d2

Please sign in to comment.