/* ATTENTION! This file (but not the functions within) is deprecated. You should probably add a new file to `./helpers/` instead of adding a new function here. One-function-per-file is a better pattern than what we have here. If you're refactoring something in here, feel free to break it out to a file in `./helpers` if you have the time. */ import Im from "immutable" import { sanitizeUrl as braintreeSanitizeUrl } from "@braintree/sanitize-url" import camelCase from "lodash/camelCase" import upperFirst from "lodash/upperFirst" import _memoize from "lodash/memoize" import find from "lodash/find" import some from "lodash/some" import eq from "lodash/eq" import { memoizedSampleFromSchema, memoizedCreateXMLExample } from "core/plugins/samples/fn" import win from "./window" import cssEscape from "css.escape" import getParameterSchema from "../helpers/get-parameter-schema" import randomBytes from "randombytes" import shaJs from "sha.js" const DEFAULT_RESPONSE_KEY = "default" export const isImmutable = (maybe) => Im.Iterable.isIterable(maybe) export function isJSONObject (str) { try { var o = JSON.parse(str) // Handle non-exception-throwing cases: // Neither JSON.parse(false) or JSON.parse(1234) throw errors, hence the type-checking, // but... JSON.parse(null) returns null, and typeof null === "object", // so we must check for that, too. Thankfully, null is falsey, so this suffices: if (o && typeof o === "object") { return o } } catch (e) { // do nothing } return false } export function objectify (thing) { if(!isObject(thing)) return {} if(isImmutable(thing)) return thing.toJS() return thing } export function arrayify (thing) { if(!thing) return [] if(thing.toArray) return thing.toArray() return normalizeArray(thing) } export function fromJSOrdered (js) { if(isImmutable(js)) return js // Can't do much here if (js instanceof win.File) return js return !isObject(js) ? js : Array.isArray(js) ? Im.Seq(js).map(fromJSOrdered).toList() : Im.OrderedMap(js).map(fromJSOrdered) } export function bindToState(obj, state) { var newObj = {} Object.keys(obj) .filter(key => typeof obj[key] === "function") .forEach(key => newObj[key] = obj[key].bind(null, state)) return newObj } export function normalizeArray(arr) { if(Array.isArray(arr)) return arr return [arr] } export function isFn(fn) { return typeof fn === "function" } export function isObject(obj) { return !!obj && typeof obj === "object" } export function isFunc(thing) { return typeof(thing) === "function" } export function isArray(thing) { return Array.isArray(thing) } // I've changed memoize libs more than once, so I'm using this a way to make that simpler export const memoize = _memoize export function objMap(obj, fn) { return Object.keys(obj).reduce((newObj, key) => { newObj[key] = fn(obj[key], key) return newObj }, {}) } export function objReduce(obj, fn) { return Object.keys(obj).reduce((newObj, key) => { let res = fn(obj[key], key) if(res && typeof res === "object") Object.assign(newObj, res) return newObj }, {}) } // Redux middleware that exposes the system to async actions (like redux-thunk, but with out system instead of (dispatch, getState) export function systemThunkMiddleware(getSystem) { return ({ dispatch, getState }) => { // eslint-disable-line no-unused-vars return next => action => { if (typeof action === "function") { return action(getSystem()) } return next(action) } } } export function defaultStatusCode ( responses ) { let codes = responses.keySeq() return codes.contains(DEFAULT_RESPONSE_KEY) ? DEFAULT_RESPONSE_KEY : codes.filter( key => (key+"")[0] === "2").sort().first() } /** * Returns an Immutable List, safely * @param {Immutable.Iterable} iterable the iterable to get the key from * @param {String|[String]} key either an array of keys, or a single key * @returns {Immutable.List} either iterable.get(keys) or an empty Immutable.List */ export function getList(iterable, keys) { if(!Im.Iterable.isIterable(iterable)) { return Im.List() } let val = iterable.getIn(Array.isArray(keys) ? keys : [keys]) return Im.List.isList(val) ? val : Im.List() } /** * Adapted from http://github.com/asvd/microlight * @copyright 2016 asvd */ export function highlight (el) { const MAX_LENGTH = 5000 var _document = document, appendChild = "appendChild", test = "test" if (!el) return "" if (el.textContent.length > MAX_LENGTH) { return el.textContent } var reset = function(el) { var text = el.textContent, pos = 0, // current position next1 = text[0], // next character chr = 1, // current character prev1, // previous character prev2, // the one before the previous token = // current token content el.innerHTML = "", // (and cleaning the node) // current token type: // 0: anything else (whitespaces / newlines) // 1: operator or brace // 2: closing braces (after which '/' is division not regex) // 3: (key)word // 4: regex // 5: string starting with " // 6: string starting with ' // 7: xml comment // 8: multiline comment /* */ // 9: single-line comment starting with two slashes // // 10: single-line comment starting with hash # tokenType = 0, // kept to determine between regex and division lastTokenType, // flag determining if token is multi-character multichar, node // running through characters and highlighting while (prev2 = prev1, // escaping if needed (with except for comments) // previous character will not be therefore // recognized as a token finalize condition prev1 = tokenType < 7 && prev1 == "\\" ? 1 : chr ) { chr = next1 next1=text[++pos] multichar = token.length > 1 // checking if current token should be finalized if (!chr || // end of content // types 9-10 (single-line comments) end with a // newline (tokenType > 8 && chr == "\n") || [ // finalize conditions for other token types // 0: whitespaces /\S/[test](chr), // merged together // 1: operators 1, // consist of a single character // 2: braces 1, // consist of a single character // 3: (key)word !/[$\w]/[test](chr), // 4: regex (prev1 == "/" || prev1 == "\n") && multichar, // 5: string with " prev1 == "\"" && multichar, // 6: string with ' prev1 == "'" && multichar, // 7: xml comment text[pos-4]+prev2+prev1 == "-->", // 8: multiline comment prev2+prev1 == "*/" ][tokenType] ) { // appending the token to the result if (token) { // remapping token type into style // (some types are highlighted similarly) el[appendChild]( node = _document.createElement("span") ).setAttribute("style", [ // 0: not formatted "color: #555; font-weight: bold;", // 1: keywords "", // 2: punctuation "", // 3: strings and regexps "color: #555;", // 4: comments "" ][ // not formatted !tokenType ? 0 : // punctuation tokenType < 3 ? 2 : // comments tokenType > 6 ? 4 : // regex and strings tokenType > 3 ? 3 : // otherwise tokenType == 3, (key)word // (1 if regexp matches, 0 otherwise) + /^(a(bstract|lias|nd|rguments|rray|s(m|sert)?|uto)|b(ase|egin|ool(ean)?|reak|yte)|c(ase|atch|har|hecked|lass|lone|ompl|onst|ontinue)|de(bugger|cimal|clare|f(ault|er)?|init|l(egate|ete)?)|do|double|e(cho|ls?if|lse(if)?|nd|nsure|num|vent|x(cept|ec|p(licit|ort)|te(nds|nsion|rn)))|f(allthrough|alse|inal(ly)?|ixed|loat|or(each)?|riend|rom|unc(tion)?)|global|goto|guard|i(f|mp(lements|licit|ort)|n(it|clude(_once)?|line|out|stanceof|t(erface|ernal)?)?|s)|l(ambda|et|ock|ong)|m(icrolight|odule|utable)|NaN|n(amespace|ative|ext|ew|il|ot|ull)|o(bject|perator|r|ut|verride)|p(ackage|arams|rivate|rotected|rotocol|ublic)|r(aise|e(adonly|do|f|gister|peat|quire(_once)?|scue|strict|try|turn))|s(byte|ealed|elf|hort|igned|izeof|tatic|tring|truct|ubscript|uper|ynchronized|witch)|t(emplate|hen|his|hrows?|ransient|rue|ry|ype(alias|def|id|name|of))|u(n(checked|def(ined)?|ion|less|signed|til)|se|sing)|v(ar|irtual|oid|olatile)|w(char_t|hen|here|hile|ith)|xor|yield)$/[test](token) ]) node[appendChild](_document.createTextNode(token)) } // saving the previous token type // (skipping whitespaces and comments) lastTokenType = (tokenType && tokenType < 7) ? tokenType : lastTokenType // initializing a new token token = "" // determining the new token type (going up the // list until matching a token type start // condition) tokenType = 11 while (![ 1, // 0: whitespace // 1: operator or braces /[\/{}[(\-+*=<>:;|\\.,?!&@~]/[test](chr), // eslint-disable-line no-useless-escape /[\])]/[test](chr), // 2: closing brace /[$\w]/[test](chr), // 3: (key)word chr == "/" && // 4: regex // previous token was an // opening brace or an // operator (otherwise // division, not a regex) (lastTokenType < 2) && // workaround for xml // closing tags prev1 != "<", chr == "\"", // 5: string with " chr == "'", // 6: string with ' // 7: xml comment chr+next1+text[pos+1]+text[pos+2] == "" } else { return null } } return memoizedCreateXMLExample(schema, config) } const res = memoizedSampleFromSchema(schema, config) return typeof res === "object" ? JSON.stringify(res, null, 2) : res } export const parseSearch = () => { let map = {} let search = win.location.search if(!search) return {} if ( search != "" ) { let params = search.substr(1).split("&") for (let i in params) { if (!params.hasOwnProperty(i)) { continue } i = params[i].split("=") map[decodeURIComponent(i[0])] = (i[1] && decodeURIComponent(i[1])) || "" } } return map } export const serializeSearch = (searchMap) => { return Object.keys(searchMap).map(k => { return encodeURIComponent(k) + "=" + encodeURIComponent(searchMap[k]) }).join("&") } export const btoa = (str) => { let buffer if (str instanceof Buffer) { buffer = str } else { buffer = new Buffer(str.toString(), "utf-8") } return buffer.toString("base64") } export const sorters = { operationsSorter: { alpha: (a, b) => a.get("path").localeCompare(b.get("path")), method: (a, b) => a.get("method").localeCompare(b.get("method")) }, tagsSorter: { alpha: (a, b) => a.localeCompare(b) } } export const buildFormData = (data) => { let formArr = [] for (let name in data) { let val = data[name] if (val !== undefined && val !== "") { formArr.push([name, "=", encodeURIComponent(val).replace(/%20/g,"+")].join("")) } } return formArr.join("&") } // Is this really required as a helper? Perhaps. TODO: expose the system of presets.apis in docs, so we know what is supported export const shallowEqualKeys = (a,b, keys) => { return !!find(keys, (key) => { return eq(a[key], b[key]) }) } export function sanitizeUrl(url) { if(typeof url !== "string" || url === "") { return "" } return braintreeSanitizeUrl(url) } export function getAcceptControllingResponse(responses) { if(!Im.OrderedMap.isOrderedMap(responses)) { // wrong type! return null } if(!responses.size) { // responses is empty return null } const suitable2xxResponse = responses.find((res, k) => { return k.startsWith("2") && Object.keys(res.get("content") || {}).length > 0 }) // try to find a suitable `default` responses const defaultResponse = responses.get("default") || Im.OrderedMap() const defaultResponseMediaTypes = (defaultResponse.get("content") || Im.OrderedMap()).keySeq().toJS() const suitableDefaultResponse = defaultResponseMediaTypes.length ? defaultResponse : null return suitable2xxResponse || suitableDefaultResponse } // suitable for use in URL fragments export const createDeepLinkPath = (str) => typeof str == "string" || str instanceof String ? str.trim().replace(/\s/g, "%20") : "" // suitable for use in CSS classes and ids export const escapeDeepLinkPath = (str) => cssEscape( createDeepLinkPath(str).replace(/%20/g, "_") ) export const getExtensions = (defObj) => defObj.filter((v, k) => /^x-/.test(k)) export const getCommonExtensions = (defObj) => defObj.filter((v, k) => /^pattern|maxLength|minLength|maximum|minimum/.test(k)) // Deeply strips a specific key from an object. // // `predicate` can be used to discriminate the stripping further, // by preserving the key's place in the object based on its value. export function deeplyStripKey(input, keyToStrip, predicate = () => true) { if(typeof input !== "object" || Array.isArray(input) || input === null || !keyToStrip) { return input } const obj = Object.assign({}, input) Object.keys(obj).forEach(k => { if(k === keyToStrip && predicate(obj[k], k)) { delete obj[k] return } obj[k] = deeplyStripKey(obj[k], keyToStrip, predicate) }) return obj } export function stringify(thing) { if (typeof thing === "string") { return thing } if (thing && thing.toJS) { thing = thing.toJS() } if (typeof thing === "object" && thing !== null) { try { return JSON.stringify(thing, null, 2) } catch (e) { return String(thing) } } if(thing === null || thing === undefined) { return "" } return thing.toString() } export function numberToString(thing) { if(typeof thing === "number") { return thing.toString() } return thing } export function paramToIdentifier(param, { returnAll = false, allowHashes = true } = {}) { if(!Im.Map.isMap(param)) { throw new Error("paramToIdentifier: received a non-Im.Map parameter as input") } const paramName = param.get("name") const paramIn = param.get("in") let generatedIdentifiers = [] // Generate identifiers in order of most to least specificity if (param && param.hashCode && paramIn && paramName && allowHashes) { generatedIdentifiers.push(`${paramIn}.${paramName}.hash-${param.hashCode()}`) } if(paramIn && paramName) { generatedIdentifiers.push(`${paramIn}.${paramName}`) } generatedIdentifiers.push(paramName) // Return the most preferred identifier, or all if requested return returnAll ? generatedIdentifiers : (generatedIdentifiers[0] || "") } export function paramToValue(param, paramValues) { const allIdentifiers = paramToIdentifier(param, { returnAll: true }) // Map identifiers to values in the provided value hash, filter undefined values, // and return the first value found const values = allIdentifiers .map(id => { return paramValues[id] }) .filter(value => value !== undefined) return values[0] } // adapted from https://auth0.com/docs/flows/guides/auth-code-pkce/includes/create-code-verifier export function generateCodeVerifier() { return b64toB64UrlEncoded( randomBytes(32).toString("base64") ) } export function createCodeChallenge(codeVerifier) { return b64toB64UrlEncoded( shaJs("sha256") .update(codeVerifier) .digest("base64") ) } function b64toB64UrlEncoded(str) { return str .replace(/\+/g, "-") .replace(/\//g, "_") .replace(/=/g, "") }