title: Upload stream to Dropbox session upload
body: <p>I have a form that accepts a file to pass on to Dropbox using the session upload. I've wrapped the code in a Fastify server but always end up with an error in append:</p>
  <pre><code>{&quot;error_summary&quot;: &quot;closed/..&quot;, &quot;error&quot;: {&quot;.tag&quot;: &quot;closed&quot;}}.
  </code></pre>
  <p>Here's the Fastify server:</p>
  <pre><code>require(&quot;dotenv&quot;).config()
  const https = require(&quot;https&quot;)
  const JSONbig = require(&quot;json-bigint&quot;)
  
  const TOKEN = process.env.DROPBOX_ACCESS_TOKEN
  
  const fastify = require(&quot;fastify&quot;)({
    logger: {
      level: &quot;info&quot;
    }
  })
  
  fastify.addContentTypeParser(&quot;*&quot;, function (req, done) {
    done()
  })
  
  // Promisify https request
  const httpRequest = (url, options, data) =&gt; {
    return new Promise((resolve, reject) =&gt; {
      const req = https.request(url, options, (res) =&gt; {
        let body = &quot;&quot;
        res.on(&quot;data&quot;, (chunk) =&gt; {
          console.log(&quot;SDSDSDSDASDASDSAD&quot;)
          body += chunk.toString()
        })
        res.on(&quot;error&quot;, reject)
        res.on(&quot;end&quot;, () =&gt; {
          if (res.statusCode &gt;= 200 &amp;&amp; res.statusCode &lt;= 299) {
            resolve({ statusCode: res.statusCode, headers: res.headers, body: JSON.parse(body) })
          } else {
            reject(&quot;Request failed. status: &quot; + res.statusCode + &quot;, body: &quot; + body)
          }
        })
      })
      req.on(&quot;error&quot;, reject)
      if (data) req.write(data)
      req.end()
    })
  }
  
  fastify.register(require(&quot;fastify-cors&quot;))
  
  fastify.post(&quot;/&quot;, async (req, reply) =&gt; {
    const stream = req.raw
  
    const initResponse = await httpRequest(
      &quot;https://content.dropboxapi.com/2/files/upload_session/start&quot;,
      {
        method: &quot;post&quot;,
  
        headers: {
          Authorization: `Bearer ${TOKEN}`,
          &quot;Dropbox-API-Arg&quot;: JSON.stringify({
            close: false
          }),
          &quot;Content-Type&quot;: &quot;application/octet-stream&quot;
        }
      }
    )
  
    const sessionId = initResponse.body.session_id
  
    req.log.info(`Initiating session ${sessionId}`)
  
    let offset = 0
  
    stream.on(&quot;data&quot;, async (chunk) =&gt; {
      req.log.info(&quot;Sending chunk&quot;)
      stream.pause()
  
      const appendResponse = await httpRequest(
        `https://content.dropboxapi.com/2/files/upload_session/append_v2`,
  
        {
          method: &quot;post&quot;,
          headers: {
            Authorization: `Bearer ${TOKEN}`,
            &quot;Dropbox-API-Arg&quot;: JSONbig.stringify({
              cursor: {
                session_id: sessionId,
                offset
              },
              close: false
            }),
            &quot;Content-Type&quot;: &quot;application/octet-stream&quot;
          }
        },
        chunk
      )
  
      stream.resume()
      offset += chunk.length
    })
    stream.on(&quot;end&quot;, async () =&gt; {
      const terminateResponse = await httpRequest(
        `https://content.dropboxapi.com/2/files/upload_session/finish`,
  
        {
          method: &quot;post&quot;,
          headers: {
            Authorization: `Bearer ${TOKEN}`,
            &quot;Dropbox-API-Arg&quot;: JSONbig.stringify({
              cursor: {
                session_id: sessionId,
                offset: offset
              },
              commit: {
                path: &quot;/Upload/test-large.txt&quot;,
                mode: &quot;add&quot;,
                autorename: true,
                mute: false,
                strict_conflict: false
              }
            }),
            &quot;Content-Type&quot;: &quot;application/octet-stream&quot;
          }
        }
      )
  
      req.log.info(&quot;upload session finished&quot;)
  
      reply.code(204).send()
    })
  })
  
  fastify.listen(3000, (err) =&gt; {
    if (err) {
      fastify.log.fatal(err)
      process.exit(1)
    }
    console.log(`server listening on ${fastify.server.address().port}`)
  })
  
  </code></pre>
  <p>And here's the form:</p>
  <pre><code>&lt;!DOCTYPE html&gt;
  &lt;!-- Coding By CodingNepal - youtube.com/codingnepal --&gt;
  &lt;html lang=&quot;en&quot;&gt;
    &lt;head&gt;
      &lt;meta charset=&quot;UTF-8&quot; /&gt;
      &lt;meta name=&quot;viewport&quot; content=&quot;width=device-width, initial-scale=1.0&quot; /&gt;
      &lt;title&gt;File Upload with Progress Bar | CodingNepal&lt;/title&gt;
      &lt;link
        rel=&quot;stylesheet&quot;
        href=&quot;https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.3/css/all.min.css&quot;
      /&gt;
      &lt;style&gt;
        /* Import Google font - Poppins */
        @import url(&quot;https://fonts.googleapis.com/css2?family=Poppins:wght@400;500;600&amp;display=swap&quot;);
        * {
          margin: 0;
          padding: 0;
          box-sizing: border-box;
          font-family: &quot;Poppins&quot;, sans-serif;
        }
        body {
          display: flex;
          align-items: center;
          justify-content: center;
          min-height: 100vh;
          background: #6990f2;
        }
  
        ::selection {
          color: #fff;
          background: #6990f2;
        }
        .wrapper {
          width: 430px;
          background: #fff;
          border-radius: 5px;
          padding: 30px;
          box-shadow: 7px 7px 12px rgba(0, 0, 0, 0.05);
        }
        .wrapper header {
          color: #6990f2;
          font-size: 27px;
          font-weight: 600;
          text-align: center;
        }
        .wrapper form {
          height: 167px;
          display: flex;
          cursor: pointer;
          margin: 30px 0;
          align-items: center;
          justify-content: center;
          flex-direction: column;
          border-radius: 5px;
          border: 2px dashed #6990f2;
        }
        form :where(i, p) {
          color: #6990f2;
        }
        form i {
          font-size: 50px;
        }
        form p {
          margin-top: 15px;
          font-size: 16px;
        }
  
        section .row {
          margin-bottom: 10px;
          background: #e9f0ff;
          list-style: none;
          padding: 15px 20px;
          border-radius: 5px;
          display: flex;
          align-items: center;
          justify-content: space-between;
        }
        section .row i {
          color: #6990f2;
          font-size: 30px;
        }
        section .details span {
          font-size: 14px;
        }
        .progress-area .row .content {
          width: 100%;
          margin-left: 15px;
        }
        .progress-area .details {
          display: flex;
          align-items: center;
          margin-bottom: 7px;
          justify-content: space-between;
        }
        .progress-area .content .progress-bar {
          height: 6px;
          width: 100%;
          margin-bottom: 4px;
          background: #fff;
          border-radius: 30px;
        }
        .content .progress-bar .progress {
          height: 100%;
          width: 0%;
          background: #6990f2;
          border-radius: inherit;
        }
        .uploaded-area {
          max-height: 232px;
          overflow-y: scroll;
        }
        .uploaded-area.onprogress {
          max-height: 150px;
        }
        .uploaded-area::-webkit-scrollbar {
          width: 0px;
        }
        .uploaded-area .row .content {
          display: flex;
          align-items: center;
        }
        .uploaded-area .row .details {
          display: flex;
          margin-left: 15px;
          flex-direction: column;
        }
        .uploaded-area .row .details .size {
          color: #404040;
          font-size: 11px;
        }
        .uploaded-area i.fa-check {
          font-size: 16px;
        }
      &lt;/style&gt;
    &lt;/head&gt;
    &lt;body&gt;
      &lt;div class=&quot;wrapper&quot;&gt;
        &lt;header&gt;File Uploader JavaScript&lt;/header&gt;
        &lt;form action=&quot;#&quot;&gt;
          &lt;input class=&quot;file-input&quot; type=&quot;file&quot; name=&quot;file&quot; hidden /&gt;
          &lt;i class=&quot;fas fa-cloud-upload-alt&quot;&gt;&lt;/i&gt;
          &lt;p&gt;Browse File to Upload&lt;/p&gt;
        &lt;/form&gt;
        &lt;section class=&quot;progress-area&quot;&gt;&lt;/section&gt;
        &lt;section class=&quot;uploaded-area&quot;&gt;&lt;/section&gt;
      &lt;/div&gt;
      &lt;script&gt;
        const form = document.querySelector(&quot;form&quot;),
          fileInput = document.querySelector(&quot;.file-input&quot;),
          progressArea = document.querySelector(&quot;.progress-area&quot;),
          uploadedArea = document.querySelector(&quot;.uploaded-area&quot;)
  
        // form click event
        form.addEventListener(&quot;click&quot;, () =&gt; {
          fileInput.click()
        })
  
        fileInput.onchange = ({ target }) =&gt; {
          let file = target.files[0] //getting file [0] this means if user has selected multiple files then get first one only
          if (file) {
            let fileName = file.name //getting file name
            if (fileName.length &gt;= 12) {
              //if file name length is greater than 12 then split it and add ...
              let splitName = fileName.split(&quot;.&quot;)
              fileName = splitName[0].substring(0, 13) + &quot;... .&quot; + splitName[1]
            }
            uploadFile(fileName) //calling uploadFile with passing file name as an argument
          }
        }
  
        // file upload function
        function uploadFile(name) {
          let xhr = new XMLHttpRequest() //creating new xhr object (AJAX)
          xhr.open(&quot;POST&quot;, &quot;http://localhost:3000&quot;) //sending post request to the specified URL
          xhr.upload.addEventListener(&quot;progress&quot;, ({ loaded, total }) =&gt; {
            //file uploading progress event
            let fileLoaded = Math.floor((loaded / total) * 100) //getting percentage of loaded file size
            let fileTotal = Math.floor(total / 1000) //gettting total file size in KB from bytes
            let fileSize
            // if file size is less than 1024 then add only KB else convert this KB into MB
            fileTotal &lt; 1024
              ? (fileSize = fileTotal + &quot; KB&quot;)
              : (fileSize = (loaded / (1024 * 1024)).toFixed(2) + &quot; MB&quot;)
            let progressHTML = `&lt;li class=&quot;row&quot;&gt;
                              &lt;i class=&quot;fas fa-file-alt&quot;&gt;&lt;/i&gt;
                              &lt;div class=&quot;content&quot;&gt;
                                &lt;div class=&quot;details&quot;&gt;
                                  &lt;span class=&quot;name&quot;&gt;${name} • Uploading&lt;/span&gt;
                                  &lt;span class=&quot;percent&quot;&gt;${fileLoaded}%&lt;/span&gt;
                                &lt;/div&gt;
                                &lt;div class=&quot;progress-bar&quot;&gt;
                                  &lt;div class=&quot;progress&quot; style=&quot;width: ${fileLoaded}%&quot;&gt;&lt;/div&gt;
                                &lt;/div&gt;
                              &lt;/div&gt;
                            &lt;/li&gt;`
            // uploadedArea.innerHTML = &quot;&quot;; //uncomment this line if you don't want to show upload history
            uploadedArea.classList.add(&quot;onprogress&quot;)
            progressArea.innerHTML = progressHTML
            if (loaded == total) {
              progressArea.innerHTML = &quot;&quot;
              let uploadedHTML = `&lt;li class=&quot;row&quot;&gt;
                                &lt;div class=&quot;content upload&quot;&gt;
                                  &lt;i class=&quot;fas fa-file-alt&quot;&gt;&lt;/i&gt;
                                  &lt;div class=&quot;details&quot;&gt;
                                    &lt;span class=&quot;name&quot;&gt;${name} • Uploaded&lt;/span&gt;
                                    &lt;span class=&quot;size&quot;&gt;${fileSize}&lt;/span&gt;
                                  &lt;/div&gt;
                                &lt;/div&gt;
                                &lt;i class=&quot;fas fa-check&quot;&gt;&lt;/i&gt;
                              &lt;/li&gt;`
              uploadedArea.classList.remove(&quot;onprogress&quot;)
              // uploadedArea.innerHTML = uploadedHTML; //uncomment this line if you don't want to show upload history
              uploadedArea.insertAdjacentHTML(&quot;afterbegin&quot;, uploadedHTML) //remove this line if you don't want to show upload history
            }
          })
          let data = new FormData(form) //FormData is an object to easily send form data
          xhr.send(data) //sending form data
        }
      &lt;/script&gt;
    &lt;/body&gt;
  &lt;/html&gt;
  
  </code></pre>
  <p>Does need an app created on DB to get oauth token to run example.</p>
  <p>Anyone see the issue here? I've worked from a number of examples online but all of them assume I am using some dummy static file rather than incoming uploaded file.</p>