/**
* @license apache-2.0
* @file util.js
* Copyright (c) 2012-2020, LGS Innovations Inc., All rights reserved.
*
* This file is part of SigFile.
*
* Licensed to the LGS Innovations (LGS) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. LGS licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
/**
* @namespace util
*/
/**
* Returns the endianness of the browser
*
* @example
* // returns true
* const whichEndian = endianness();
* whichEndian === "LE"
*
* @memberof util
* @private
* @returns {string} "LE" for Little Endian and "BE" for Big Endian
* @throws {Error} Throws Error if endianness is unknown
* @see {@link https://gist.github.com/TooTallNate/4750953}
*/
function endianness() {
const b = new ArrayBuffer(4);
const a = new Uint32Array(b);
const c = new Uint8Array(b);
a[0] = 0xdeadbeef;
if (c[0] === 0xef) {
return 'LE';
} else if (c[0] === 0xde) {
return 'BE';
} else {
throw new Error('unknown endianness');
}
}
/**
* JS implementation of Python's dict.update method.
* This updates object `dst` with the properties in `src`.
*
* Note: This has been deprecated in v0.1.4 in favor
* of the faster `Object.assign(target, source)`.
*
* @deprecated since v0.1.4
*
* @example <caption>Update object</caption>
* // returns {a: 1, b: 2, c: 1, d: 3}
* const d1 = {a: 1, b: 2};
* const d2 = {c: 1, d: 3};
* update(d1, d2) // d1 is now `{a: 1, b: 2, c: 1, d: 3}`
*
* @memberOf util
* @param {object} dst - The object that will be updated
* @param {object} src - The object whose properties will be added to `dst`
* @returns {object} The updated `dst`. This is only returned for chaining.
*/
function update(dst, src) {
for (let prop in src) {
if (Object.prototype.hasOwnProperty.call(src, prop)) {
const val = src[prop];
if (typeof val === 'object') {
// recursive
if (dst[prop] === undefined) {
dst[prop] = {};
}
update(dst[prop], val);
} else {
dst[prop] = val;
}
}
}
return dst; // return dst to allow method chaining
}
/**
* Returns the 64-bit integer from the data buffer at
* the requested offset.
*
* @memberOf util
* @param {DataView} dataView - Data buffer
* @param {number} index - The byte offset into the data buffer
* @param {boolean} littleEndian - The endianness of the data
* @returns {number} The 64-bit integer from the `dataView`
*/
function getInt64(dataView, index, littleEndian) {
const MAX_INT = Math.pow(2, 53);
const [highIndex, lowIndex] = littleEndian ? [4, 0] : [0, 4];
const high = dataView.getInt32(index + highIndex, littleEndian);
const low = dataView.getInt32(index + lowIndex, littleEndian);
const rv = low + pow2(32) * high;
if (rv >= MAX_INT) {
console.warn('Int is bigger than JS can represent.');
return Infinity;
} else {
return rv;
}
}
/**
* Determine whether `String.fromCharCode.apply` supports
* typed arrays as input.
*
* @example
* // returns true
* applySupportsTypedArray()
*
* @memberOf util
* @returns {boolean} Whether `String.fromCharCode.apply` supports typed arrays
*/
function applySupportsTypedArray() {
try {
const uintbuf = new Uint8Array(new ArrayBuffer(4));
uintbuf[0] = 66;
uintbuf[1] = 76;
uintbuf[2] = 85;
uintbuf[3] = 69;
const test = String.fromCharCode.apply(null, uintbuf);
if (test !== 'BLUE') {
return false;
}
} catch (error) {
return false;
}
return true;
}
/**
* Convert an ArrayBuffer to a string
*
* @memberOf util
* @param {ArrayBuffer | Array} buf - Data buffer
* @param {boolean} [apply=undefined] - whether or not apply supports typed arrays
* @returns {string} The string representation of the data buffer
*/
function ab2str(buf, apply) {
const uintbuf = new Uint8Array(buf);
// Set `_applySupportsTypedArray` as static variable
if (typeof ab2str._applySupportsTypedArray == 'undefined') {
// It has not... perform the initialization
if (apply !== undefined) {
ab2str._applySupportsTypedArray = apply;
} else {
ab2str._applySupportsTypedArray = applySupportsTypedArray();
}
}
// Firefox 3.6 nor iOS devices can use ArrayBuffers with .apply
if (ab2str._applySupportsTypedArray) {
return String.fromCharCode.apply(null, uintbuf);
} else {
return uintbuf.reduce((prev, curr) => {
return prev + String.fromCharCode(curr);
}, '');
}
}
/**
* Convert a string to an ArrayBuffer
*
* @memberOf util
* @param {string} str - The string being turning into an ArrayBuffer
* @returns {ArrayBuffer} The ArrayBuffer representation of the string
*/
function str2ab(str) {
const buf = new ArrayBuffer(str.length * 2); // 2 bytes for each char
const bufView = new Uint16Array(buf);
for (let i = 0, strLen = str.length; i < strLen; i++) {
bufView[i] = str.charCodeAt(i);
}
return buf;
}
/**
* Calculate $2^n$
*
* If 31 > n >= 0 then a left-shift is used, otherwise Math.pow is used.
*
* @example
* const result = pow2(5); // == 32
*
* @memberOf util
* @param {number} n - The exponent that we're raising 2 to -- i.e., 2^n
* @returns {number} The result of 2^n
*/
function pow2(n) {
return n >= 0 && n < 31 ? 1 << n : pow2[n] || (pow2[n] = Math.pow(2, n));
}
/**
* Internal method to create a new anchor element and uses location
* properties (inherent) to get the desired URL data. Some String
* operations are used (to normalize results across browsers).
*
* @memberOf util
* @param {string} url - Properly formatted URL
* @returns {object} Object containing URL pieces parsed out
*
* @see {@link http://james.padolsey.com/javascript/parsing-urls-with-the-dom/}
*/
function parseURL(url) {
const a = document.createElement('a');
a.href = url;
return {
source: url,
protocol: a.protocol.replace(':', ''),
host: a.hostname,
port: a.port,
query: a.search,
params: (function () {
const ret = {},
seg = a.search.replace(/^\?/, '').split('&'),
len = seg.length;
for (let i = 0; i < len; i++) {
if (!seg[i]) {
continue;
}
let s = seg[i].split('=');
ret[s[0]] = s[1];
}
return ret;
})(),
file: (a.pathname.match(/\/([^/?#]+)$/i) || [null, ''])[1],
hash: a.hash.replace('#', ''),
path: a.pathname.replace(/^([^/])/, '/$1'),
relative: (a.href.match(/tps?:\/\/[^/]+(.+)/) || [null, ''])[1],
segments: a.pathname.replace(/^\//, '').split('/'),
};
}
/**
* @callback onComplete
* @param {ArrayBuffer} x - Callback for when text from HTTP response
* has been converted into an ArrayBuffer
*/
/**
* Internal method to convert text from an HTTP response
* into an ArrayBuffer.
*
* @memberOf util
* @param {string} text - Text from HTTP response being converted
* @param {onComplete} oncomplete - Callback that will run after text is converted
* @param {number} [blocksize=1024] How much data we're expecting
*/
function text2buffer(text, oncomplete, blocksize) {
blocksize = blocksize || 1024;
let i = 0;
const arrayBuffer = new ArrayBuffer(text.length);
const bufView = new Uint8Array(arrayBuffer);
const worker = () => {
const end = i + blocksize;
for (; i < end; i++) {
bufView[i] = text.charCodeAt(i) & 0xff;
}
if (i >= text.length) {
oncomplete(arrayBuffer);
} else {
setTimeout(worker, 0);
}
};
setTimeout(worker, 0);
}
export {
applySupportsTypedArray,
endianness,
update,
getInt64,
ab2str,
str2ab,
pow2,
parseURL,
text2buffer,
};