CoolFace
Apppublic

strong-tie/inbound-calls

sourceHugging Faceupdated 2y agoView on Hugging Face
0likes
FormData.js442 linesDownload Raw Back to formdata-polyfill
1/* formdata-polyfill. MIT License. Jimmy Wärting <https://jimmy.warting.se/opensource> */2 3/* global FormData self Blob File */4/* eslint-disable no-inner-declarations */5 6if (typeof Blob !== 'undefined' && (typeof FormData === 'undefined' || !FormData.prototype.keys)) {7  const global = typeof globalThis === 'object'8    ? globalThis9    : typeof window === 'object'10      ? window11      : typeof self === 'object' ? self : this12 13  // keep a reference to native implementation14  const _FormData = global.FormData15 16  // To be monkey patched17  const _send = global.XMLHttpRequest && global.XMLHttpRequest.prototype.send18  const _fetch = global.Request && global.fetch19  const _sendBeacon = global.navigator && global.navigator.sendBeacon20  // Might be a worker thread...21  const _match = global.Element && global.Element.prototype22 23  // Unable to patch Request/Response constructor correctly #10924  // only way is to use ES6 class extend25  // https://github.com/babel/babel/issues/196626 27  const stringTag = global.Symbol && Symbol.toStringTag28 29  // Add missing stringTags to blob and files30  if (stringTag) {31    if (!Blob.prototype[stringTag]) {32      Blob.prototype[stringTag] = 'Blob'33    }34 35    if ('File' in global && !File.prototype[stringTag]) {36      File.prototype[stringTag] = 'File'37    }38  }39 40  // Fix so you can construct your own File41  try {42    new File([], '') // eslint-disable-line43  } catch (a) {44    global.File = function File (b, d, c) {45      const blob = new Blob(b, c || {})46      const t = c && void 0 !== c.lastModified ? new Date(c.lastModified) : new Date()47 48      Object.defineProperties(blob, {49        name: {50          value: d51        },52        lastModified: {53          value: +t54        },55        toString: {56          value () {57            return '[object File]'58          }59        }60      })61 62      if (stringTag) {63        Object.defineProperty(blob, stringTag, {64          value: 'File'65        })66      }67 68      return blob69    }70  }71 72  function ensureArgs (args, expected) {73    if (args.length < expected) {74      throw new TypeError(`${expected} argument required, but only ${args.length} present.`)75    }76  }77 78  /**79   * @param {string} name80   * @param {string | undefined} filename81   * @returns {[string, File|string]}82   */83  function normalizeArgs (name, value, filename) {84    if (value instanceof Blob) {85      filename = filename !== undefined86      ? String(filename + '')87      : typeof value.name === 'string'88      ? value.name89      : 'blob'90 91      if (value.name !== filename || Object.prototype.toString.call(value) === '[object Blob]') {92        value = new File([value], filename)93      }94      return [String(name), value]95    }96    return [String(name), String(value)]97  }98 99  // normalize line feeds for textarea100  // https://html.spec.whatwg.org/multipage/form-elements.html#textarea-line-break-normalisation-transformation101  function normalizeLinefeeds (value) {102    return value.replace(/\r?\n|\r/g, '\r\n')103  }104 105  /**106   * @template T107   * @param {ArrayLike<T>} arr108   * @param {{ (elm: T): void; }} cb109   */110  function each (arr, cb) {111    for (let i = 0; i < arr.length; i++) {112      cb(arr[i])113    }114  }115 116  const escape = str => str.replace(/\n/g, '%0A').replace(/\r/g, '%0D').replace(/"/g, '%22')117 118  /**119   * @implements {Iterable}120   */121  class FormDataPolyfill {122    /**123     * FormData class124     *125     * @param {HTMLFormElement=} form126     */127    constructor (form) {128      /** @type {[string, string|File][]} */129      this._data = []130 131      const self = this132      form && each(form.elements, (/** @type {HTMLInputElement} */ elm) => {133        if (134          !elm.name ||135          elm.disabled ||136          elm.type === 'submit' ||137          elm.type === 'button' ||138          elm.matches('form fieldset[disabled] *')139        ) return140 141        if (elm.type === 'file') {142          const files = elm.files && elm.files.length143            ? elm.files144            : [new File([], '', { type: 'application/octet-stream' })] // #78145 146          each(files, file => {147            self.append(elm.name, file)148          })149        } else if (elm.type === 'select-multiple' || elm.type === 'select-one') {150          each(elm.options, opt => {151            !opt.disabled && opt.selected && self.append(elm.name, opt.value)152          })153        } else if (elm.type === 'checkbox' || elm.type === 'radio') {154          if (elm.checked) self.append(elm.name, elm.value)155        } else {156          const value = elm.type === 'textarea' ? normalizeLinefeeds(elm.value) : elm.value157          self.append(elm.name, value)158        }159      })160    }161 162    /**163     * Append a field164     *165     * @param   {string}           name      field name166     * @param   {string|Blob|File} value     string / blob / file167     * @param   {string=}          filename  filename to use with blob168     * @return  {undefined}169     */170    append (name, value, filename) {171      ensureArgs(arguments, 2)172      this._data.push(normalizeArgs(name, value, filename))173    }174 175    /**176     * Delete all fields values given name177     *178     * @param   {string}  name  Field name179     * @return  {undefined}180     */181    delete (name) {182      ensureArgs(arguments, 1)183      const result = []184      name = String(name)185 186      each(this._data, entry => {187        entry[0] !== name && result.push(entry)188      })189 190      this._data = result191    }192 193    /**194     * Iterate over all fields as [name, value]195     *196     * @return {Iterator}197     */198    * entries () {199      for (var i = 0; i < this._data.length; i++) {200        yield this._data[i]201      }202    }203 204    /**205     * Iterate over all fields206     *207     * @param   {Function}  callback  Executed for each item with parameters (value, name, thisArg)208     * @param   {Object=}   thisArg   `this` context for callback function209     */210    forEach (callback, thisArg) {211      ensureArgs(arguments, 1)212      for (const [name, value] of this) {213        callback.call(thisArg, value, name, this)214      }215    }216 217    /**218     * Return first field value given name219     * or null if non existent220     *221     * @param   {string}  name      Field name222     * @return  {string|File|null}  value Fields value223     */224    get (name) {225      ensureArgs(arguments, 1)226      const entries = this._data227      name = String(name)228      for (let i = 0; i < entries.length; i++) {229        if (entries[i][0] === name) {230          return entries[i][1]231        }232      }233      return null234    }235 236    /**237     * Return all fields values given name238     *239     * @param   {string}  name  Fields name240     * @return  {Array}         [{String|File}]241     */242    getAll (name) {243      ensureArgs(arguments, 1)244      const result = []245      name = String(name)246      each(this._data, data => {247        data[0] === name && result.push(data[1])248      })249 250      return result251    }252 253    /**254     * Check for field name existence255     *256     * @param   {string}   name  Field name257     * @return  {boolean}258     */259    has (name) {260      ensureArgs(arguments, 1)261      name = String(name)262      for (let i = 0; i < this._data.length; i++) {263        if (this._data[i][0] === name) {264          return true265        }266      }267      return false268    }269 270    /**271     * Iterate over all fields name272     *273     * @return {Iterator}274     */275    * keys () {276      for (const [name] of this) {277        yield name278      }279    }280 281    /**282     * Overwrite all values given name283     *284     * @param   {string}    name      Filed name285     * @param   {string}    value     Field value286     * @param   {string=}   filename  Filename (optional)287     */288    set (name, value, filename) {289      ensureArgs(arguments, 2)290      name = String(name)291      /** @type {[string, string|File][]} */292      const result = []293      const args = normalizeArgs(name, value, filename)294      let replace = true295 296      // - replace the first occurrence with same name297      // - discards the remaining with same name298      // - while keeping the same order items where added299      each(this._data, data => {300        data[0] === name301          ? replace && (replace = !result.push(args))302          : result.push(data)303      })304 305      replace && result.push(args)306 307      this._data = result308    }309 310    /**311     * Iterate over all fields312     *313     * @return {Iterator}314     */315    * values () {316      for (const [, value] of this) {317        yield value318      }319    }320 321    /**322     * Return a native (perhaps degraded) FormData with only a `append` method323     * Can throw if it's not supported324     *325     * @return {FormData}326     */327    ['_asNative'] () {328      const fd = new _FormData()329 330      for (const [name, value] of this) {331        fd.append(name, value)332      }333 334      return fd335    }336 337    /**338     * [_blob description]339     *340     * @return {Blob} [description]341     */342    ['_blob'] () {343        const boundary = '----formdata-polyfill-' + Math.random(),344          chunks = [],345          p = `--${boundary}\r\nContent-Disposition: form-data; name="`346        this.forEach((value, name) => typeof value == 'string'347          ? chunks.push(p + escape(normalizeLinefeeds(name)) + `"\r\n\r\n${normalizeLinefeeds(value)}\r\n`)348          : chunks.push(p + escape(normalizeLinefeeds(name)) + `"; filename="${escape(value.name)}"\r\nContent-Type: ${value.type||"application/octet-stream"}\r\n\r\n`, value, `\r\n`))349        chunks.push(`--${boundary}--`)350        return new Blob(chunks, {351          type: "multipart/form-data; boundary=" + boundary352        })353    }354 355    /**356     * The class itself is iterable357     * alias for formdata.entries()358     *359     * @return {Iterator}360     */361    [Symbol.iterator] () {362      return this.entries()363    }364 365    /**366     * Create the default string description.367     *368     * @return  {string} [object FormData]369     */370    toString () {371      return '[object FormData]'372    }373  }374 375  if (_match && !_match.matches) {376    _match.matches =377      _match.matchesSelector ||378      _match.mozMatchesSelector ||379      _match.msMatchesSelector ||380      _match.oMatchesSelector ||381      _match.webkitMatchesSelector ||382      function (s) {383        var matches = (this.document || this.ownerDocument).querySelectorAll(s)384        var i = matches.length385        while (--i >= 0 && matches.item(i) !== this) {}386        return i > -1387      }388  }389 390  if (stringTag) {391    /**392     * Create the default string description.393     * It is accessed internally by the Object.prototype.toString().394     */395    FormDataPolyfill.prototype[stringTag] = 'FormData'396  }397 398  // Patch xhr's send method to call _blob transparently399  if (_send) {400    const setRequestHeader = global.XMLHttpRequest.prototype.setRequestHeader401 402    global.XMLHttpRequest.prototype.setRequestHeader = function (name, value) {403      setRequestHeader.call(this, name, value)404      if (name.toLowerCase() === 'content-type') this._hasContentType = true405    }406 407    global.XMLHttpRequest.prototype.send = function (data) {408      // need to patch send b/c old IE don't send blob's type (#44)409      if (data instanceof FormDataPolyfill) {410        const blob = data['_blob']()411        if (!this._hasContentType) this.setRequestHeader('Content-Type', blob.type)412        _send.call(this, blob)413      } else {414        _send.call(this, data)415      }416    }417  }418 419  // Patch fetch's function to call _blob transparently420  if (_fetch) {421    global.fetch = function (input, init) {422      if (init && init.body && init.body instanceof FormDataPolyfill) {423        init.body = init.body['_blob']()424      }425 426      return _fetch.call(this, input, init)427    }428  }429 430  // Patch navigator.sendBeacon to use native FormData431  if (_sendBeacon) {432    global.navigator.sendBeacon = function (url, data) {433      if (data instanceof FormDataPolyfill) {434        data = data['_asNative']()435      }436      return _sendBeacon.call(this, url, data)437    }438  }439 440  global['FormData'] = FormDataPolyfill441}442