@stacks/blockchain-api-client
Version:
Client for the Stacks Blockchain API
1,623 lines (1,550 loc) • 180 kB
JavaScript
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
typeof define === 'function' && define.amd ? define(['exports'], factory) :
(global = global || self, factory(global.StacksBlockchainApiClient = {}));
})(this, (function (exports) {
function _extends() {
return _extends = Object.assign ? Object.assign.bind() : function (n) {
for (var e = 1; e < arguments.length; e++) {
var t = arguments[e];
for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]);
}
return n;
}, _extends.apply(null, arguments);
}
// settings & const
const DEFAULT_HEADERS = {
"Content-Type": "application/json",
};
const PATH_PARAM_RE = /\{[^{}]+\}/g;
/** Add custom parameters to Request object */
class CustomRequest extends Request {
constructor(input, init) {
super(input, init);
// add custom parameters
for (const key in init) {
if (!(key in this)) {
this[key] = init[key];
}
}
}
}
/**
* Returns a cheap, non-cryptographically-secure random ID
* Courtesy of @imranbarbhuiya (https://github.com/imranbarbhuiya)
*/
function randomID() {
return Math.random().toString(36).slice(2, 11);
}
/**
* Create an openapi-fetch client.
* @type {import("./index.js").default}
*/
function createClient$1(clientOptions) {
let {
baseUrl = "",
fetch: baseFetch = globalThis.fetch,
querySerializer: globalQuerySerializer,
bodySerializer: globalBodySerializer,
headers: baseHeaders,
...baseOptions
} = { ...clientOptions };
if (baseUrl.endsWith("/")) {
baseUrl = baseUrl.substring(0, baseUrl.length - 1);
}
baseHeaders = mergeHeaders(DEFAULT_HEADERS, baseHeaders);
const middlewares = [];
/**
* Per-request fetch (keeps settings created in createClient()
* @param {T} url
* @param {import('./index.js').FetchOptions<T>} fetchOptions
*/
async function coreFetch(schemaPath, fetchOptions) {
const {
fetch = baseFetch,
headers,
params = {},
parseAs = "json",
querySerializer: requestQuerySerializer,
bodySerializer = globalBodySerializer ?? defaultBodySerializer,
...init
} = fetchOptions || {};
let querySerializer =
typeof globalQuerySerializer === "function"
? globalQuerySerializer
: createQuerySerializer(globalQuerySerializer);
if (requestQuerySerializer) {
querySerializer =
typeof requestQuerySerializer === "function"
? requestQuerySerializer
: createQuerySerializer({
...(typeof globalQuerySerializer === "object" ? globalQuerySerializer : {}),
...requestQuerySerializer,
});
}
const requestInit = {
redirect: "follow",
...baseOptions,
...init,
headers: mergeHeaders(baseHeaders, headers, params.header),
};
if (requestInit.body) {
requestInit.body = bodySerializer(requestInit.body);
// remove `Content-Type` if serialized body is FormData; browser will correctly set Content-Type & boundary expression
if (requestInit.body instanceof FormData) {
requestInit.headers.delete("Content-Type");
}
}
let id;
let options;
let request = new CustomRequest(createFinalURL(schemaPath, { baseUrl, params, querySerializer }), requestInit);
if (middlewares.length) {
id = randomID();
// middleware (request)
options = Object.freeze({
baseUrl,
fetch,
parseAs,
querySerializer,
bodySerializer,
});
for (const m of middlewares) {
if (m && typeof m === "object" && typeof m.onRequest === "function") {
const result = await m.onRequest({
request,
schemaPath,
params,
options,
id,
});
if (result) {
if (!(result instanceof Request)) {
throw new Error("onRequest: must return new Request() when modifying the request");
}
request = result;
}
}
}
}
// fetch!
let response = await fetch(request);
// middleware (response)
// execute in reverse-array order (first priority gets last transform)
if (middlewares.length) {
for (let i = middlewares.length - 1; i >= 0; i--) {
const m = middlewares[i];
if (m && typeof m === "object" && typeof m.onResponse === "function") {
const result = await m.onResponse({
request,
response,
schemaPath,
params,
options,
id,
});
if (result) {
if (!(result instanceof Response)) {
throw new Error("onResponse: must return new Response() when modifying the response");
}
response = result;
}
}
}
}
// handle empty content
// note: we return `{}` because we want user truthy checks for `.data` or `.error` to succeed
if (response.status === 204 || response.headers.get("Content-Length") === "0") {
return response.ok ? { data: {}, response } : { error: {}, response };
}
// parse response (falling back to .text() when necessary)
if (response.ok) {
// if "stream", skip parsing entirely
if (parseAs === "stream") {
return { data: response.body, response };
}
return { data: await response[parseAs](), response };
}
// handle errors
let error = await response.text();
try {
error = JSON.parse(error); // attempt to parse as JSON
} catch {
// noop
}
return { error, response };
}
return {
/** Call a GET endpoint */
GET(url, init) {
return coreFetch(url, { ...init, method: "GET" });
},
/** Call a PUT endpoint */
PUT(url, init) {
return coreFetch(url, { ...init, method: "PUT" });
},
/** Call a POST endpoint */
POST(url, init) {
return coreFetch(url, { ...init, method: "POST" });
},
/** Call a DELETE endpoint */
DELETE(url, init) {
return coreFetch(url, { ...init, method: "DELETE" });
},
/** Call a OPTIONS endpoint */
OPTIONS(url, init) {
return coreFetch(url, { ...init, method: "OPTIONS" });
},
/** Call a HEAD endpoint */
HEAD(url, init) {
return coreFetch(url, { ...init, method: "HEAD" });
},
/** Call a PATCH endpoint */
PATCH(url, init) {
return coreFetch(url, { ...init, method: "PATCH" });
},
/** Call a TRACE endpoint */
TRACE(url, init) {
return coreFetch(url, { ...init, method: "TRACE" });
},
/** Register middleware */
use(...middleware) {
for (const m of middleware) {
if (!m) {
continue;
}
if (typeof m !== "object" || !("onRequest" in m || "onResponse" in m)) {
throw new Error("Middleware must be an object with one of `onRequest()` or `onResponse()`");
}
middlewares.push(m);
}
},
/** Unregister middleware */
eject(...middleware) {
for (const m of middleware) {
const i = middlewares.indexOf(m);
if (i !== -1) {
middlewares.splice(i, 1);
}
}
},
};
}
// utils
/**
* Serialize primitive param values
* @type {import("./index.js").serializePrimitiveParam}
*/
function serializePrimitiveParam(name, value, options) {
if (value === undefined || value === null) {
return "";
}
if (typeof value === "object") {
throw new Error(
"Deeply-nested arrays/objects aren’t supported. Provide your own `querySerializer()` to handle these.",
);
}
return `${name}=${options?.allowReserved === true ? value : encodeURIComponent(value)}`;
}
/**
* Serialize object param (shallow only)
* @type {import("./index.js").serializeObjectParam}
*/
function serializeObjectParam(name, value, options) {
if (!value || typeof value !== "object") {
return "";
}
const values = [];
const joiner =
{
simple: ",",
label: ".",
matrix: ";",
}[options.style] || "&";
// explode: false
if (options.style !== "deepObject" && options.explode === false) {
for (const k in value) {
values.push(k, options.allowReserved === true ? value[k] : encodeURIComponent(value[k]));
}
const final = values.join(","); // note: values are always joined by comma in explode: false (but joiner can prefix)
switch (options.style) {
case "form": {
return `${name}=${final}`;
}
case "label": {
return `.${final}`;
}
case "matrix": {
return `;${name}=${final}`;
}
default: {
return final;
}
}
}
// explode: true
for (const k in value) {
const finalName = options.style === "deepObject" ? `${name}[${k}]` : k;
values.push(serializePrimitiveParam(finalName, value[k], options));
}
const final = values.join(joiner);
return options.style === "label" || options.style === "matrix" ? `${joiner}${final}` : final;
}
/**
* Serialize array param (shallow only)
* @type {import("./index.js").serializeArrayParam}
*/
function serializeArrayParam(name, value, options) {
if (!Array.isArray(value)) {
return "";
}
// explode: false
if (options.explode === false) {
const joiner = { form: ",", spaceDelimited: "%20", pipeDelimited: "|" }[options.style] || ","; // note: for arrays, joiners vary wildly based on style + explode behavior
const final = (options.allowReserved === true ? value : value.map((v) => encodeURIComponent(v))).join(joiner);
switch (options.style) {
case "simple": {
return final;
}
case "label": {
return `.${final}`;
}
case "matrix": {
return `;${name}=${final}`;
}
// case "spaceDelimited":
// case "pipeDelimited":
default: {
return `${name}=${final}`;
}
}
}
// explode: true
const joiner = { simple: ",", label: ".", matrix: ";" }[options.style] || "&";
const values = [];
for (const v of value) {
if (options.style === "simple" || options.style === "label") {
values.push(options.allowReserved === true ? v : encodeURIComponent(v));
} else {
values.push(serializePrimitiveParam(name, v, options));
}
}
return options.style === "label" || options.style === "matrix"
? `${joiner}${values.join(joiner)}`
: values.join(joiner);
}
/**
* Serialize query params to string
* @type {import("./index.js").createQuerySerializer}
*/
function createQuerySerializer(options) {
return function querySerializer(queryParams) {
const search = [];
if (queryParams && typeof queryParams === "object") {
for (const name in queryParams) {
const value = queryParams[name];
if (value === undefined || value === null) {
continue;
}
if (Array.isArray(value)) {
search.push(
serializeArrayParam(name, value, {
style: "form",
explode: true,
...options?.array,
allowReserved: options?.allowReserved || false,
}),
);
continue;
}
if (typeof value === "object") {
search.push(
serializeObjectParam(name, value, {
style: "deepObject",
explode: true,
...options?.object,
allowReserved: options?.allowReserved || false,
}),
);
continue;
}
search.push(serializePrimitiveParam(name, value, options));
}
}
return search.join("&");
};
}
/**
* Handle different OpenAPI 3.x serialization styles
* @type {import("./index.js").defaultPathSerializer}
* @see https://swagger.io/docs/specification/serialization/#path
*/
function defaultPathSerializer(pathname, pathParams) {
let nextURL = pathname;
for (const match of pathname.match(PATH_PARAM_RE) ?? []) {
let name = match.substring(1, match.length - 1);
let explode = false;
let style = "simple";
if (name.endsWith("*")) {
explode = true;
name = name.substring(0, name.length - 1);
}
if (name.startsWith(".")) {
style = "label";
name = name.substring(1);
} else if (name.startsWith(";")) {
style = "matrix";
name = name.substring(1);
}
if (!pathParams || pathParams[name] === undefined || pathParams[name] === null) {
continue;
}
const value = pathParams[name];
if (Array.isArray(value)) {
nextURL = nextURL.replace(match, serializeArrayParam(name, value, { style, explode }));
continue;
}
if (typeof value === "object") {
nextURL = nextURL.replace(match, serializeObjectParam(name, value, { style, explode }));
continue;
}
if (style === "matrix") {
nextURL = nextURL.replace(match, `;${serializePrimitiveParam(name, value)}`);
continue;
}
nextURL = nextURL.replace(match, style === "label" ? `.${encodeURIComponent(value)}` : encodeURIComponent(value));
}
return nextURL;
}
/**
* Serialize body object to string
* @type {import("./index.js").defaultBodySerializer}
*/
function defaultBodySerializer(body) {
if (body instanceof FormData) {
return body;
}
return JSON.stringify(body);
}
/**
* Construct URL string from baseUrl and handle path and query params
* @type {import("./index.js").createFinalURL}
*/
function createFinalURL(pathname, options) {
let finalURL = `${options.baseUrl}${pathname}`;
if (options.params?.path) {
finalURL = defaultPathSerializer(finalURL, options.params.path);
}
let search = options.querySerializer(options.params.query ?? {});
if (search.startsWith("?")) {
search = search.substring(1);
}
if (search) {
finalURL += `?${search}`;
}
return finalURL;
}
/**
* Merge headers a and b, with b taking priority
* @type {import("./index.js").mergeHeaders}
*/
function mergeHeaders(...allHeaders) {
const finalHeaders = new Headers();
for (const h of allHeaders) {
if (!h || typeof h !== "object") {
continue;
}
const iterator = h instanceof Headers ? h.entries() : Object.entries(h);
for (const [k, v] of iterator) {
if (v === null) {
finalHeaders.delete(k);
} else if (Array.isArray(v)) {
for (const v2 of v) {
finalHeaders.append(k, v2);
}
} else if (v !== undefined) {
finalHeaders.set(k, v);
}
}
}
return finalHeaders;
}
var BASE_PATH = "https://api.mainnet.hiro.so";
const PACKET_TYPES = Object.create(null); // no Map = no polyfill
PACKET_TYPES["open"] = "0";
PACKET_TYPES["close"] = "1";
PACKET_TYPES["ping"] = "2";
PACKET_TYPES["pong"] = "3";
PACKET_TYPES["message"] = "4";
PACKET_TYPES["upgrade"] = "5";
PACKET_TYPES["noop"] = "6";
const PACKET_TYPES_REVERSE = Object.create(null);
Object.keys(PACKET_TYPES).forEach((key) => {
PACKET_TYPES_REVERSE[PACKET_TYPES[key]] = key;
});
const ERROR_PACKET = { type: "error", data: "parser error" };
const withNativeBlob$1 = typeof Blob === "function" ||
(typeof Blob !== "undefined" &&
Object.prototype.toString.call(Blob) === "[object BlobConstructor]");
const withNativeArrayBuffer$2 = typeof ArrayBuffer === "function";
// ArrayBuffer.isView method is not defined in IE10
const isView$1 = (obj) => {
return typeof ArrayBuffer.isView === "function"
? ArrayBuffer.isView(obj)
: obj && obj.buffer instanceof ArrayBuffer;
};
const encodePacket = ({ type, data }, supportsBinary, callback) => {
if (withNativeBlob$1 && data instanceof Blob) {
if (supportsBinary) {
return callback(data);
}
else {
return encodeBlobAsBase64(data, callback);
}
}
else if (withNativeArrayBuffer$2 &&
(data instanceof ArrayBuffer || isView$1(data))) {
if (supportsBinary) {
return callback(data);
}
else {
return encodeBlobAsBase64(new Blob([data]), callback);
}
}
// plain string
return callback(PACKET_TYPES[type] + (data || ""));
};
const encodeBlobAsBase64 = (data, callback) => {
const fileReader = new FileReader();
fileReader.onload = function () {
const content = fileReader.result.split(",")[1];
callback("b" + (content || ""));
};
return fileReader.readAsDataURL(data);
};
function toArray(data) {
if (data instanceof Uint8Array) {
return data;
}
else if (data instanceof ArrayBuffer) {
return new Uint8Array(data);
}
else {
return new Uint8Array(data.buffer, data.byteOffset, data.byteLength);
}
}
let TEXT_ENCODER;
function encodePacketToBinary(packet, callback) {
if (withNativeBlob$1 && packet.data instanceof Blob) {
return packet.data.arrayBuffer().then(toArray).then(callback);
}
else if (withNativeArrayBuffer$2 &&
(packet.data instanceof ArrayBuffer || isView$1(packet.data))) {
return callback(toArray(packet.data));
}
encodePacket(packet, false, (encoded) => {
if (!TEXT_ENCODER) {
TEXT_ENCODER = new TextEncoder();
}
callback(TEXT_ENCODER.encode(encoded));
});
}
// imported from https://github.com/socketio/base64-arraybuffer
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
// Use a lookup table to find the index.
const lookup$1 = typeof Uint8Array === 'undefined' ? [] : new Uint8Array(256);
for (let i = 0; i < chars.length; i++) {
lookup$1[chars.charCodeAt(i)] = i;
}
const decode$1 = (base64) => {
let bufferLength = base64.length * 0.75, len = base64.length, i, p = 0, encoded1, encoded2, encoded3, encoded4;
if (base64[base64.length - 1] === '=') {
bufferLength--;
if (base64[base64.length - 2] === '=') {
bufferLength--;
}
}
const arraybuffer = new ArrayBuffer(bufferLength), bytes = new Uint8Array(arraybuffer);
for (i = 0; i < len; i += 4) {
encoded1 = lookup$1[base64.charCodeAt(i)];
encoded2 = lookup$1[base64.charCodeAt(i + 1)];
encoded3 = lookup$1[base64.charCodeAt(i + 2)];
encoded4 = lookup$1[base64.charCodeAt(i + 3)];
bytes[p++] = (encoded1 << 2) | (encoded2 >> 4);
bytes[p++] = ((encoded2 & 15) << 4) | (encoded3 >> 2);
bytes[p++] = ((encoded3 & 3) << 6) | (encoded4 & 63);
}
return arraybuffer;
};
const withNativeArrayBuffer$1 = typeof ArrayBuffer === "function";
const decodePacket = (encodedPacket, binaryType) => {
if (typeof encodedPacket !== "string") {
return {
type: "message",
data: mapBinary(encodedPacket, binaryType),
};
}
const type = encodedPacket.charAt(0);
if (type === "b") {
return {
type: "message",
data: decodeBase64Packet(encodedPacket.substring(1), binaryType),
};
}
const packetType = PACKET_TYPES_REVERSE[type];
if (!packetType) {
return ERROR_PACKET;
}
return encodedPacket.length > 1
? {
type: PACKET_TYPES_REVERSE[type],
data: encodedPacket.substring(1),
}
: {
type: PACKET_TYPES_REVERSE[type],
};
};
const decodeBase64Packet = (data, binaryType) => {
if (withNativeArrayBuffer$1) {
const decoded = decode$1(data);
return mapBinary(decoded, binaryType);
}
else {
return { base64: true, data }; // fallback for old browsers
}
};
const mapBinary = (data, binaryType) => {
switch (binaryType) {
case "blob":
if (data instanceof Blob) {
// from WebSocket + binaryType "blob"
return data;
}
else {
// from HTTP long-polling or WebTransport
return new Blob([data]);
}
case "arraybuffer":
default:
if (data instanceof ArrayBuffer) {
// from HTTP long-polling (base64) or WebSocket + binaryType "arraybuffer"
return data;
}
else {
// from WebTransport (Uint8Array)
return data.buffer;
}
}
};
const SEPARATOR = String.fromCharCode(30); // see https://en.wikipedia.org/wiki/Delimiter#ASCII_delimited_text
const encodePayload = (packets, callback) => {
// some packets may be added to the array while encoding, so the initial length must be saved
const length = packets.length;
const encodedPackets = new Array(length);
let count = 0;
packets.forEach((packet, i) => {
// force base64 encoding for binary packets
encodePacket(packet, false, (encodedPacket) => {
encodedPackets[i] = encodedPacket;
if (++count === length) {
callback(encodedPackets.join(SEPARATOR));
}
});
});
};
const decodePayload = (encodedPayload, binaryType) => {
const encodedPackets = encodedPayload.split(SEPARATOR);
const packets = [];
for (let i = 0; i < encodedPackets.length; i++) {
const decodedPacket = decodePacket(encodedPackets[i], binaryType);
packets.push(decodedPacket);
if (decodedPacket.type === "error") {
break;
}
}
return packets;
};
function createPacketEncoderStream() {
return new TransformStream({
transform(packet, controller) {
encodePacketToBinary(packet, (encodedPacket) => {
const payloadLength = encodedPacket.length;
let header;
// inspired by the WebSocket format: https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API/Writing_WebSocket_servers#decoding_payload_length
if (payloadLength < 126) {
header = new Uint8Array(1);
new DataView(header.buffer).setUint8(0, payloadLength);
}
else if (payloadLength < 65536) {
header = new Uint8Array(3);
const view = new DataView(header.buffer);
view.setUint8(0, 126);
view.setUint16(1, payloadLength);
}
else {
header = new Uint8Array(9);
const view = new DataView(header.buffer);
view.setUint8(0, 127);
view.setBigUint64(1, BigInt(payloadLength));
}
// first bit indicates whether the payload is plain text (0) or binary (1)
if (packet.data && typeof packet.data !== "string") {
header[0] |= 0x80;
}
controller.enqueue(header);
controller.enqueue(encodedPacket);
});
},
});
}
let TEXT_DECODER;
function totalLength(chunks) {
return chunks.reduce((acc, chunk) => acc + chunk.length, 0);
}
function concatChunks(chunks, size) {
if (chunks[0].length === size) {
return chunks.shift();
}
const buffer = new Uint8Array(size);
let j = 0;
for (let i = 0; i < size; i++) {
buffer[i] = chunks[0][j++];
if (j === chunks[0].length) {
chunks.shift();
j = 0;
}
}
if (chunks.length && j < chunks[0].length) {
chunks[0] = chunks[0].slice(j);
}
return buffer;
}
function createPacketDecoderStream(maxPayload, binaryType) {
if (!TEXT_DECODER) {
TEXT_DECODER = new TextDecoder();
}
const chunks = [];
let state = 0 /* State.READ_HEADER */;
let expectedLength = -1;
let isBinary = false;
return new TransformStream({
transform(chunk, controller) {
chunks.push(chunk);
while (true) {
if (state === 0 /* State.READ_HEADER */) {
if (totalLength(chunks) < 1) {
break;
}
const header = concatChunks(chunks, 1);
isBinary = (header[0] & 0x80) === 0x80;
expectedLength = header[0] & 0x7f;
if (expectedLength < 126) {
state = 3 /* State.READ_PAYLOAD */;
}
else if (expectedLength === 126) {
state = 1 /* State.READ_EXTENDED_LENGTH_16 */;
}
else {
state = 2 /* State.READ_EXTENDED_LENGTH_64 */;
}
}
else if (state === 1 /* State.READ_EXTENDED_LENGTH_16 */) {
if (totalLength(chunks) < 2) {
break;
}
const headerArray = concatChunks(chunks, 2);
expectedLength = new DataView(headerArray.buffer, headerArray.byteOffset, headerArray.length).getUint16(0);
state = 3 /* State.READ_PAYLOAD */;
}
else if (state === 2 /* State.READ_EXTENDED_LENGTH_64 */) {
if (totalLength(chunks) < 8) {
break;
}
const headerArray = concatChunks(chunks, 8);
const view = new DataView(headerArray.buffer, headerArray.byteOffset, headerArray.length);
const n = view.getUint32(0);
if (n > Math.pow(2, 53 - 32) - 1) {
// the maximum safe integer in JavaScript is 2^53 - 1
controller.enqueue(ERROR_PACKET);
break;
}
expectedLength = n * Math.pow(2, 32) + view.getUint32(4);
state = 3 /* State.READ_PAYLOAD */;
}
else {
if (totalLength(chunks) < expectedLength) {
break;
}
const data = concatChunks(chunks, expectedLength);
controller.enqueue(decodePacket(isBinary ? data : TEXT_DECODER.decode(data), binaryType));
state = 0 /* State.READ_HEADER */;
}
if (expectedLength === 0 || expectedLength > maxPayload) {
controller.enqueue(ERROR_PACKET);
break;
}
}
},
});
}
const protocol$1 = 4;
/**
* Initialize a new `Emitter`.
*
* @api public
*/
function Emitter(obj) {
if (obj) return mixin(obj);
}
/**
* Mixin the emitter properties.
*
* @param {Object} obj
* @return {Object}
* @api private
*/
function mixin(obj) {
for (var key in Emitter.prototype) {
obj[key] = Emitter.prototype[key];
}
return obj;
}
/**
* Listen on the given `event` with `fn`.
*
* @param {String} event
* @param {Function} fn
* @return {Emitter}
* @api public
*/
Emitter.prototype.on =
Emitter.prototype.addEventListener = function(event, fn){
this._callbacks = this._callbacks || {};
(this._callbacks['$' + event] = this._callbacks['$' + event] || [])
.push(fn);
return this;
};
/**
* Adds an `event` listener that will be invoked a single
* time then automatically removed.
*
* @param {String} event
* @param {Function} fn
* @return {Emitter}
* @api public
*/
Emitter.prototype.once = function(event, fn){
function on() {
this.off(event, on);
fn.apply(this, arguments);
}
on.fn = fn;
this.on(event, on);
return this;
};
/**
* Remove the given callback for `event` or all
* registered callbacks.
*
* @param {String} event
* @param {Function} fn
* @return {Emitter}
* @api public
*/
Emitter.prototype.off =
Emitter.prototype.removeListener =
Emitter.prototype.removeAllListeners =
Emitter.prototype.removeEventListener = function(event, fn){
this._callbacks = this._callbacks || {};
// all
if (0 == arguments.length) {
this._callbacks = {};
return this;
}
// specific event
var callbacks = this._callbacks['$' + event];
if (!callbacks) return this;
// remove all handlers
if (1 == arguments.length) {
delete this._callbacks['$' + event];
return this;
}
// remove specific handler
var cb;
for (var i = 0; i < callbacks.length; i++) {
cb = callbacks[i];
if (cb === fn || cb.fn === fn) {
callbacks.splice(i, 1);
break;
}
}
// Remove event specific arrays for event types that no
// one is subscribed for to avoid memory leak.
if (callbacks.length === 0) {
delete this._callbacks['$' + event];
}
return this;
};
/**
* Emit `event` with the given args.
*
* @param {String} event
* @param {Mixed} ...
* @return {Emitter}
*/
Emitter.prototype.emit = function(event){
this._callbacks = this._callbacks || {};
var args = new Array(arguments.length - 1)
, callbacks = this._callbacks['$' + event];
for (var i = 1; i < arguments.length; i++) {
args[i - 1] = arguments[i];
}
if (callbacks) {
callbacks = callbacks.slice(0);
for (var i = 0, len = callbacks.length; i < len; ++i) {
callbacks[i].apply(this, args);
}
}
return this;
};
// alias used for reserved events (protected method)
Emitter.prototype.emitReserved = Emitter.prototype.emit;
/**
* Return array of callbacks for `event`.
*
* @param {String} event
* @return {Array}
* @api public
*/
Emitter.prototype.listeners = function(event){
this._callbacks = this._callbacks || {};
return this._callbacks['$' + event] || [];
};
/**
* Check if this emitter has `event` handlers.
*
* @param {String} event
* @return {Boolean}
* @api public
*/
Emitter.prototype.hasListeners = function(event){
return !! this.listeners(event).length;
};
const globalThisShim = (() => {
if (typeof self !== "undefined") {
return self;
}
else if (typeof window !== "undefined") {
return window;
}
else {
return Function("return this")();
}
})();
function pick(obj, ...attr) {
return attr.reduce((acc, k) => {
if (obj.hasOwnProperty(k)) {
acc[k] = obj[k];
}
return acc;
}, {});
}
// Keep a reference to the real timeout functions so they can be used when overridden
const NATIVE_SET_TIMEOUT = globalThisShim.setTimeout;
const NATIVE_CLEAR_TIMEOUT = globalThisShim.clearTimeout;
function installTimerFunctions(obj, opts) {
if (opts.useNativeTimers) {
obj.setTimeoutFn = NATIVE_SET_TIMEOUT.bind(globalThisShim);
obj.clearTimeoutFn = NATIVE_CLEAR_TIMEOUT.bind(globalThisShim);
}
else {
obj.setTimeoutFn = globalThisShim.setTimeout.bind(globalThisShim);
obj.clearTimeoutFn = globalThisShim.clearTimeout.bind(globalThisShim);
}
}
// base64 encoded buffers are about 33% bigger (https://en.wikipedia.org/wiki/Base64)
const BASE64_OVERHEAD = 1.33;
// we could also have used `new Blob([obj]).size`, but it isn't supported in IE9
function byteLength(obj) {
if (typeof obj === "string") {
return utf8Length(obj);
}
// arraybuffer or blob
return Math.ceil((obj.byteLength || obj.size) * BASE64_OVERHEAD);
}
function utf8Length(str) {
let c = 0, length = 0;
for (let i = 0, l = str.length; i < l; i++) {
c = str.charCodeAt(i);
if (c < 0x80) {
length += 1;
}
else if (c < 0x800) {
length += 2;
}
else if (c < 0xd800 || c >= 0xe000) {
length += 3;
}
else {
i++;
length += 4;
}
}
return length;
}
// imported from https://github.com/galkn/querystring
/**
* Compiles a querystring
* Returns string representation of the object
*
* @param {Object}
* @api private
*/
function encode$1(obj) {
let str = '';
for (let i in obj) {
if (obj.hasOwnProperty(i)) {
if (str.length)
str += '&';
str += encodeURIComponent(i) + '=' + encodeURIComponent(obj[i]);
}
}
return str;
}
/**
* Parses a simple querystring into an object
*
* @param {String} qs
* @api private
*/
function decode(qs) {
let qry = {};
let pairs = qs.split('&');
for (let i = 0, l = pairs.length; i < l; i++) {
let pair = pairs[i].split('=');
qry[decodeURIComponent(pair[0])] = decodeURIComponent(pair[1]);
}
return qry;
}
class TransportError extends Error {
constructor(reason, description, context) {
super(reason);
this.description = description;
this.context = context;
this.type = "TransportError";
}
}
class Transport extends Emitter {
/**
* Transport abstract constructor.
*
* @param {Object} opts - options
* @protected
*/
constructor(opts) {
super();
this.writable = false;
installTimerFunctions(this, opts);
this.opts = opts;
this.query = opts.query;
this.socket = opts.socket;
}
/**
* Emits an error.
*
* @param {String} reason
* @param description
* @param context - the error context
* @return {Transport} for chaining
* @protected
*/
onError(reason, description, context) {
super.emitReserved("error", new TransportError(reason, description, context));
return this;
}
/**
* Opens the transport.
*/
open() {
this.readyState = "opening";
this.doOpen();
return this;
}
/**
* Closes the transport.
*/
close() {
if (this.readyState === "opening" || this.readyState === "open") {
this.doClose();
this.onClose();
}
return this;
}
/**
* Sends multiple packets.
*
* @param {Array} packets
*/
send(packets) {
if (this.readyState === "open") {
this.write(packets);
}
}
/**
* Called upon open
*
* @protected
*/
onOpen() {
this.readyState = "open";
this.writable = true;
super.emitReserved("open");
}
/**
* Called with data.
*
* @param {String} data
* @protected
*/
onData(data) {
const packet = decodePacket(data, this.socket.binaryType);
this.onPacket(packet);
}
/**
* Called with a decoded packet.
*
* @protected
*/
onPacket(packet) {
super.emitReserved("packet", packet);
}
/**
* Called upon close.
*
* @protected
*/
onClose(details) {
this.readyState = "closed";
super.emitReserved("close", details);
}
/**
* Pauses the transport, in order not to lose packets during an upgrade.
*
* @param onPause
*/
pause(onPause) { }
createUri(schema, query = {}) {
return (schema +
"://" +
this._hostname() +
this._port() +
this.opts.path +
this._query(query));
}
_hostname() {
const hostname = this.opts.hostname;
return hostname.indexOf(":") === -1 ? hostname : "[" + hostname + "]";
}
_port() {
if (this.opts.port &&
((this.opts.secure && Number(this.opts.port !== 443)) ||
(!this.opts.secure && Number(this.opts.port) !== 80))) {
return ":" + this.opts.port;
}
else {
return "";
}
}
_query(query) {
const encodedQuery = encode$1(query);
return encodedQuery.length ? "?" + encodedQuery : "";
}
}
// imported from https://github.com/unshiftio/yeast
const alphabet = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-_'.split(''), length = 64;
let seed = 0, prev;
/**
* Return a string representing the specified number.
*
* @param {Number} num The number to convert.
* @returns {String} The string representation of the number.
* @api public
*/
function encode(num) {
let encoded = '';
do {
encoded = alphabet[num % length] + encoded;
num = Math.floor(num / length);
} while (num > 0);
return encoded;
}
/**
* Yeast: A tiny growing id generator.
*
* @returns {String} A unique id.
* @api public
*/
function yeast() {
const now = encode(+new Date());
if (now !== prev)
return seed = 0, prev = now;
return now + '.' + encode(seed++);
}
// imported from https://github.com/component/has-cors
let value = false;
try {
value = typeof XMLHttpRequest !== 'undefined' &&
'withCredentials' in new XMLHttpRequest();
}
catch (err) {
// if XMLHttp support is disabled in IE then it will throw
// when trying to create
}
const hasCORS = value;
// browser shim for xmlhttprequest module
function XHR(opts) {
const xdomain = opts.xdomain;
// XMLHttpRequest can be disabled on IE
try {
if ("undefined" !== typeof XMLHttpRequest && (!xdomain || hasCORS)) {
return new XMLHttpRequest();
}
}
catch (e) { }
if (!xdomain) {
try {
return new globalThisShim[["Active"].concat("Object").join("X")]("Microsoft.XMLHTTP");
}
catch (e) { }
}
}
function createCookieJar() { }
function empty() { }
const hasXHR2 = (function () {
const xhr = new XHR({
xdomain: false,
});
return null != xhr.responseType;
})();
class Polling extends Transport {
/**
* XHR Polling constructor.
*
* @param {Object} opts
* @package
*/
constructor(opts) {
super(opts);
this.polling = false;
if (typeof location !== "undefined") {
const isSSL = "https:" === location.protocol;
let port = location.port;
// some user agents have empty `location.port`
if (!port) {
port = isSSL ? "443" : "80";
}
this.xd =
(typeof location !== "undefined" &&
opts.hostname !== location.hostname) ||
port !== opts.port;
}
/**
* XHR supports binary
*/
const forceBase64 = opts && opts.forceBase64;
this.supportsBinary = hasXHR2 && !forceBase64;
if (this.opts.withCredentials) {
this.cookieJar = createCookieJar();
}
}
get name() {
return "polling";
}
/**
* Opens the socket (triggers polling). We write a PING message to determine
* when the transport is open.
*
* @protected
*/
doOpen() {
this.poll();
}
/**
* Pauses polling.
*
* @param {Function} onPause - callback upon buffers are flushed and transport is paused
* @package
*/
pause(onPause) {
this.readyState = "pausing";
const pause = () => {
this.readyState = "paused";
onPause();
};
if (this.polling || !this.writable) {
let total = 0;
if (this.polling) {
total++;
this.once("pollComplete", function () {
--total || pause();
});
}
if (!this.writable) {
total++;
this.once("drain", function () {
--total || pause();
});
}
}
else {
pause();
}
}
/**
* Starts polling cycle.
*
* @private
*/
poll() {
this.polling = true;
this.doPoll();
this.emitReserved("poll");
}
/**
* Overloads onData to detect payloads.
*
* @protected
*/
onData(data) {
const callback = (packet) => {
// if its the first message we consider the transport open
if ("opening" === this.readyState && packet.type === "open") {
this.onOpen();
}
// if its a close packet, we close the ongoing requests
if ("close" === packet.type) {
this.onClose({ description: "transport closed by the server" });
return false;
}
// otherwise bypass onData and handle the message
this.onPacket(packet);
};
// decode payload
decodePayload(data, this.socket.binaryType).forEach(callback);
// if an event did not trigger closing
if ("closed" !== this.readyState) {
// if we got data we're not polling
this.polling = false;
this.emitReserved("pollComplete");
if ("open" === this.readyState) {
this.poll();
}
}
}
/**
* For polling, send a close packet.
*
* @protected
*/
doClose() {
const close = () => {
this.write([{ type: "close" }]);
};
if ("open" === this.readyState) {
close();
}
else {
// in case we're trying to close while
// handshaking is in progress (GH-164)
this.once("open", close);
}
}
/**
* Writes a packets payload.
*
* @param {Array} packets - data packets
* @protected
*/
write(packets) {
this.writable = false;
encodePayload(packets, (data) => {
this.doWrite(data, () => {
this.writable = true;
this.emitReserved("drain");
});
});
}
/**
* Generates uri for connection.
*
* @private
*/
uri() {
const schema = this.opts.secure ? "https" : "http";
const query = this.query || {};
// cache busting is forced
if (false !== this.opts.timestampRequests) {
query[this.opts.timestampParam] = yeast();
}
if (!this.supportsBinary && !query.sid) {
query.b64 = 1;
}
return this.createUri(schema, query);
}
/**
* Creates a request.
*
* @param {String} method
* @private
*/
request(opts = {}) {
Object.assign(opts, { xd: this.xd, cookieJar: this.cookieJar }, this.opts);
return new Request$1(this.uri(), opts);
}
/**
* Sends data.
*
* @param {String} data to send.
* @param {Function} called upon flush.
* @private
*/
doWrite(data, fn) {
const req = this.request({
method: "POST",
data: data,
});
req.on("success", fn);
req.on("error", (xhrStatus, context) => {
this.onError("xhr post error", xhrStatus, context);
});
}
/**
* Starts a poll cycle.
*
* @private
*/
doPoll() {
const req = this.request();
req.on("data", this.onData.bind(this));
req.on("error", (xhrStatus, context) => {
this.onError("xhr poll error", xhrStatus, context);
});
this.pollXhr = req;
}
}
class Request$1 extends Emitter {
/**
* Request constructor
*
* @param {Object} options
* @package
*/
constructor(uri, opts) {
super();
installTimerFunctions(this, opts);
this.opts = opts;
this.method = opts.method || "GET";
this.uri = uri;
this.data = undefined !== opts.data ? opts.data : null;
this.create();
}
/**
* Creates the XHR object and sends the request.
*
* @private
*/
create() {
var _a;
const opts = pick(this.opts, "agent", "pfx", "key", "passphrase", "cert", "ca", "ciphers", "rejectUnauthorized", "autoUnref");
opts.xdomain = !!this.opts.xd;
const xhr = (this.xhr = new XHR(opts));
try {
xhr.open(this.method, this.uri, true);
try {
if (this.opts.extraHeaders) {
xhr.setDisableHeaderCheck && xhr.setDisableHeaderCheck(true);
for (let i in this.opts.extraHeaders) {
if (this.opts.extraHeaders.hasOwnProperty(i)) {
xhr.setRequestHeader(i, this.opts.extraHeaders[i]);
}
}
}
}
catch (e) { }
if ("POST" === this.method) {
try {
xhr.setRequestHeader("Content-type", "text/plain;charset=UTF-8");
}
catch (e) { }
}
try {
xhr.setRequestHeader("Accept", "*/*");
}
catch (e) { }
(_a = this.opts.cookieJar) === null || _a === void 0 ? void 0 : _a.addCookies(xhr);
// ie6 check
if ("withCredentials" in xhr) {
xhr.withCredentials = this.opts.withCredentials;
}
if (this.opts.requestTimeout) {
xhr.timeout = this.opts.requestTimeout;
}
xhr.onreadystatechange = () => {
var _a;
if (xhr.readyState === 3) {
(_a = this.opts.cookieJar) === null || _a === void 0 ? void 0 : _a.parseCookies(xhr);
}
if (4 !== xhr.readyState)
return;
if (200 === xhr.status || 1223 === xhr.status) {
this.onLoad();
}
else {
// make sure the `error` event handler that's user-set
// does not throw in the same tick and gets caught here
this.setTimeoutFn(() => {
this.onError(typeof xhr.status === "number" ? xhr.status : 0);
}, 0);
}
};
xhr.send(this.data);
}
catch (e) {
// Need to defer since .create() is called directly from the constructor
// and thus the 'error' event can only be only bound *after* this exception
// occurs. Therefore, also, we cannot throw here at all.
this.setTimeoutFn(() => {
this.onError(e);
}, 0);
return;
}
if (typeof document !== "undefined") {
this.index = Request$1.requestsCount++;
Request$1.requests[this.index] = this;
}
}
/**
* Called upon error.
*
* @private
*/
onError(err) {
this.emitReserved("error", err, this.xhr);
this.cleanup(true);
}
/**
* Cleans up house.
*
* @private
*/
cleanup(fromError) {
if ("undefined" === typeof this.xhr || null === this.xhr) {
return;
}
this.xhr.onreadystatechange = empty;
if (fromError) {
try {
this.xhr.abort();
}
catch (e) { }
}
if (typeof document !== "und