Browse Source

@uppy/companion: bump Node.js version support matrix (#5035)

Co-authored-by: Mikael Finstad <finstaden@gmail.com>
Co-authored-by: Merlijn Vos <merlijn@soverin.net>
Antoine du Hamel 1 năm trước cách đây
mục cha
commit
a565c3e4a0

+ 1 - 1
.github/workflows/companion.yml

@@ -23,7 +23,7 @@ jobs:
     runs-on: ubuntu-latest
     strategy:
       matrix:
-        node-version: [14.x, 16.x, 18.x]
+        node-version: [18.x, 20.x, latest]
     steps:
       - name: Checkout sources
         uses: actions/checkout@v3

+ 10 - 13
packages/@uppy/companion/package.json

@@ -33,11 +33,10 @@
     "@aws-sdk/lib-storage": "^3.338.0",
     "@aws-sdk/s3-presigned-post": "^3.338.0",
     "@aws-sdk/s3-request-presigner": "^3.338.0",
-    "atob": "2.1.2",
-    "body-parser": "1.20.0",
+    "body-parser": "1.20.2",
     "chalk": "4.1.2",
     "common-tags": "1.8.2",
-    "connect-redis": "7.1.0",
+    "connect-redis": "7.1.1",
     "content-disposition": "^0.5.4",
     "cookie-parser": "1.4.6",
     "cors": "^2.8.5",
@@ -48,26 +47,25 @@
     "express-prom-bundle": "6.5.0",
     "express-request-id": "1.4.1",
     "express-session": "1.17.3",
-    "form-data": "^3.0.0",
-    "got": "11",
-    "grant": "5.4.21",
+    "got": "^13.0.0",
+    "grant": "5.4.22",
     "helmet": "^4.6.0",
     "ipaddr.js": "^2.0.1",
-    "jsonwebtoken": "9.0.0",
+    "jsonwebtoken": "9.0.2",
     "lodash": "^4.17.21",
     "mime-types": "2.1.35",
     "moment": "^2.29.2",
     "moment-timezone": "^0.5.31",
     "morgan": "1.10.0",
     "ms": "2.1.3",
-    "node-schedule": "2.1.0",
+    "node-schedule": "2.1.1",
     "prom-client": "14.0.1",
-    "redis": "4.2.0",
+    "redis": "4.6.13",
     "serialize-error": "^2.1.0",
     "serialize-javascript": "^6.0.0",
     "tus-js-client": "^3.1.3",
     "validator": "^13.0.0",
-    "ws": "8.8.1"
+    "ws": "8.16.0"
   },
   "devDependencies": {
     "@types/compression": "1.7.0",
@@ -84,7 +82,6 @@
     "@types/request": "2.48.8",
     "@types/webpack": "^5.28.0",
     "@types/ws": "8.5.3",
-    "into-stream": "^6.0.0",
     "jest": "^29.0.0",
     "nock": "^13.1.3",
     "supertest": "6.2.4",
@@ -109,10 +106,10 @@
     "deploy": "kubectl apply -f infra/kube/companion-kube.yml",
     "prepublishOnly": "yarn run build",
     "start": "node ./lib/standalone/start-server.js",
-    "test": "jest"
+    "test": "NODE_OPTIONS=--experimental-vm-modules jest --runInBand"
   },
   "engines": {
-    "node": "^14.19.0 || ^16.15.0 || >=18.0.0"
+    "node": "^18.20.0 || ^20.10.0 || >=22.0.0"
   },
   "installConfig": {
     "hoistingLimits": "workspaces"

+ 24 - 10
packages/@uppy/companion/src/server/Uploader.js

@@ -2,18 +2,18 @@
 const tus = require('tus-js-client')
 const { randomUUID } = require('node:crypto')
 const validator = require('validator')
-const got = require('got').default
 const { pipeline: pipelineCb } = require('node:stream')
 const { join } = require('node:path')
 const fs = require('node:fs')
 const { promisify } = require('node:util')
-const FormData = require('form-data')
 const throttle = require('lodash/throttle')
 
 const { Upload } = require('@aws-sdk/lib-storage')
 
 const { rfc2047EncodeMetadata, getBucket } = require('./helpers/utils')
 
+const got = require('./got')
+
 // TODO move to `require('streams/promises').pipeline` when dropping support for Node.js 14.x.
 const pipeline = promisify(pipelineCb)
 
@@ -142,6 +142,21 @@ const states = {
   done: 'done',
 }
 
+class StreamableBlob {
+  #stream
+
+  constructor(stream) {
+    this.#stream = stream
+  }
+
+  stream(){
+    return this.#stream
+  }
+
+  // eslint-disable-next-line class-methods-use-this
+  get [Symbol.toStringTag]() { return "File" }
+}
+
 class Uploader {
   /**
    * Uploads file to destination based on the supplied protocol (tus, s3-multipart, multipart)
@@ -613,16 +628,15 @@ class Uploader {
     }
 
     if (this.options.useFormData) {
-      // todo refactor once upgraded to got 12
       const formData = new FormData()
 
       Object.entries(this.options.metadata).forEach(([key, value]) => formData.append(key, value))
 
-      formData.append(this.options.fieldname, stream, {
-        filename: this.uploadFileName,
-        contentType: this.options.metadata.type,
-        knownLength: this.size,
-      })
+      formData.append(
+        this.options.fieldname,
+        // @ts-expect-error Our StreamableBlob is actually spec compliant enough for our purpose
+        new StreamableBlob(stream),
+        this.uploadFileName)
 
       reqOptions.body = formData
     } else {
@@ -632,7 +646,7 @@ class Uploader {
 
     try {
       const httpMethod = (this.options.httpMethod || '').toUpperCase() === 'PUT' ? 'put' : 'post'
-      const runRequest = got[httpMethod]
+      const runRequest = (await got)[httpMethod]
 
       const response = await runRequest(url, reqOptions)
 
@@ -662,7 +676,7 @@ class Uploader {
           extraData: getRespObj(err.response),
         })
       }
-      throw new Error('Unknown multipart upload error')
+      throw new Error('Unknown multipart upload error', {cause: err})
     }
   }
 

+ 0 - 1
packages/@uppy/companion/src/server/controllers/connect.js

@@ -1,4 +1,3 @@
-const atob = require('atob')
 const oAuthState = require('../helpers/oauth-state')
 
 const queryString = (params, prefix = '?') => {

+ 1 - 1
packages/@uppy/companion/src/server/controllers/url.js

@@ -25,7 +25,7 @@ const downloadURL = async (url, blockLocalIPs, traceId) => {
   // TODO in next major, rename all blockLocalIPs to allowLocalUrls and invert the bool, to make it consistent
   // see discussion https://github.com/transloadit/uppy/pull/4554/files#r1268677162
   try {
-    const protectedGot = getProtectedGot({ blockLocalIPs })
+    const protectedGot = await getProtectedGot({ blockLocalIPs })
     const stream = protectedGot.stream.get(url, { responseType: 'json' })
     await prepareStream(stream)
     return stream

+ 4 - 0
packages/@uppy/companion/src/server/got.js

@@ -0,0 +1,4 @@
+// eslint-disable-next-line import/no-unresolved
+const gotPromise = import('got')
+
+module.exports = gotPromise.then((got) => got.default)

+ 0 - 1
packages/@uppy/companion/src/server/helpers/oauth-state.js

@@ -1,5 +1,4 @@
 const crypto = require('node:crypto')
-const atob = require('atob')
 const { encrypt, decrypt } = require('./utils')
 
 module.exports.encodeState = (state, secret) => {

+ 5 - 4
packages/@uppy/companion/src/server/helpers/request.js

@@ -3,11 +3,12 @@ const http = require('node:http')
 const https = require('node:https')
 const dns = require('node:dns')
 const ipaddr = require('ipaddr.js')
-const got = require('got').default
 const path = require('node:path')
 const contentDisposition = require('content-disposition')
 const validator = require('validator')
 
+const got = require('../got')
+
 const FORBIDDEN_IP_ADDRESS = 'Forbidden IP address'
 
 // Example scary IPs that should return false (ipv6-to-ipv4 mapped):
@@ -84,7 +85,7 @@ const getProtectedHttpAgent = ({ protocol, blockLocalIPs }) => {
 
 module.exports.getProtectedHttpAgent = getProtectedHttpAgent
 
-function getProtectedGot ({ blockLocalIPs }) {
+async function getProtectedGot ({ blockLocalIPs }) {
   const HttpAgent = getProtectedHttpAgent({ protocol: 'http', blockLocalIPs })
   const HttpsAgent = getProtectedHttpAgent({ protocol: 'https', blockLocalIPs })
   const httpAgent = new HttpAgent()
@@ -92,7 +93,7 @@ function getProtectedGot ({ blockLocalIPs }) {
 
 
   // @ts-ignore
-  return got.extend({ agent: { http: httpAgent, https: httpsAgent } })
+  return (await got).extend({ agent: { http: httpAgent, https: httpsAgent } })
 }
 
 module.exports.getProtectedGot = getProtectedGot
@@ -106,7 +107,7 @@ module.exports.getProtectedGot = getProtectedGot
  */
 exports.getURLMeta = async (url, blockLocalIPs = false) => {
   async function requestWithMethod (method) {
-    const protectedGot = getProtectedGot({ blockLocalIPs })
+    const protectedGot = await getProtectedGot({ blockLocalIPs })
     const stream = protectedGot.stream(url, { method, throwHttpErrors: false })
 
     return new Promise((resolve, reject) => (

+ 3 - 2
packages/@uppy/companion/src/server/jobs.js

@@ -2,7 +2,8 @@ const schedule = require('node-schedule')
 const fs = require('node:fs')
 const path = require('node:path')
 const { promisify } = require('node:util')
-const got = require('got').default
+
+const got = require('./got')
 
 const { FILE_NAME_PREFIX } = require('./Uploader')
 const logger = require('./logger')
@@ -65,7 +66,7 @@ async function runPeriodicPing ({ urls, payload, requestTimeout }) {
   // Run requests in parallel
   await Promise.all(urls.map(async (url) => {
     try {
-      await got.post(url, { json: payload, timeout: { request: requestTimeout } })
+      await (await got).post(url, { json: payload, timeout: { request: requestTimeout } })
     } catch (err) {
       logger.warn(err, 'jobs.periodic.ping')
     }

+ 9 - 10
packages/@uppy/companion/src/server/provider/box/index.js

@@ -1,14 +1,14 @@
-const got = require('got').default
-
 const Provider = require('../Provider')
 const adaptData = require('./adapter')
 const { withProviderErrorHandling } = require('../providerErrors')
 const { prepareStream } = require('../../helpers/utils')
 
+const got = require('../../got')
+
 const BOX_FILES_FIELDS = 'id,modified_at,name,permissions,size,type'
 const BOX_THUMBNAIL_SIZE = 256
 
-const getClient = ({ token }) => got.extend({
+const getClient = async ({ token }) => (await got).extend({
   prefixUrl: 'https://api.box.com/2.0',
   headers: {
     authorization: `Bearer ${token}`,
@@ -16,13 +16,12 @@ const getClient = ({ token }) => got.extend({
 })
 
 async function getUserInfo ({ token }) {
-  return getClient({ token }).get('users/me', { responseType: 'json' }).json()
+  return (await getClient({ token })).get('users/me', { responseType: 'json' }).json()
 }
 
 async function list ({ directory, query, token }) {
   const rootFolderID = '0'
-  // https://developer.box.com/reference/resources/items/
-  return getClient({ token }).get(`folders/${directory || rootFolderID}/items`, { searchParams: { fields: BOX_FILES_FIELDS, offset: query.cursor, limit: 1000 }, responseType: 'json' }).json()
+  return (await getClient({ token })).get(`folders/${directory || rootFolderID}/items`, { searchParams: { fields: BOX_FILES_FIELDS, offset: query.cursor, limit: 1000 }, responseType: 'json' }).json()
 }
 
 /**
@@ -61,7 +60,7 @@ class Box extends Provider {
 
   async download ({ id, token }) {
     return this.#withErrorHandling('provider.box.download.error', async () => {
-      const stream = getClient({ token }).stream.get(`files/${id}/content`, { responseType: 'json' })
+      const stream = (await getClient({ token })).stream.get(`files/${id}/content`, { responseType: 'json' })
 
       await prepareStream(stream)
       return { stream }
@@ -81,7 +80,7 @@ class Box extends Provider {
       // At that time, retry this endpoint to retrieve the thumbnail.
       //
       // This can be reproduced more easily by changing extension to png and trying on a newly uploaded image
-      const stream = getClient({ token }).stream.get(`files/${id}/thumbnail.${extension}`, {
+      const stream = (await getClient({ token })).stream.get(`files/${id}/thumbnail.${extension}`, {
         searchParams: { max_height: BOX_THUMBNAIL_SIZE, max_width: BOX_THUMBNAIL_SIZE },
         responseType: 'json',
       })
@@ -93,7 +92,7 @@ class Box extends Provider {
 
   async size ({ id, token }) {
     return this.#withErrorHandling('provider.box.size.error', async () => {
-      const { size } = await getClient({ token }).get(`files/${id}`, { responseType: 'json' }).json()
+      const { size } = await (await getClient({ token })).get(`files/${id}`, { responseType: 'json' }).json()
       return parseInt(size, 10)
     })
   }
@@ -101,7 +100,7 @@ class Box extends Provider {
   logout ({ companion, token }) {
     return this.#withErrorHandling('provider.box.logout.error', async () => {
       const { key, secret } = companion.options.providerOptions.box
-      await getClient({ token }).post('oauth2/revoke', {
+      await (await getClient({ token })).post('oauth2/revoke', {
         prefixUrl: 'https://api.box.com',
         form: {
           client_id: key,

+ 3 - 3
packages/@uppy/companion/src/server/provider/credentials.js

@@ -1,5 +1,3 @@
-const got = require('got').default
-const atob = require('atob')
 const { htmlEscape } = require('escape-goat')
 const logger = require('../logger')
 const oAuthState = require('../helpers/oauth-state')
@@ -7,6 +5,8 @@ const tokenService = require('../helpers/jwt')
 // eslint-disable-next-line
 const Provider = require('./Provider')
 
+const got = require('../got')
+
 /**
  * @param {string} url
  * @param {string} providerName
@@ -14,7 +14,7 @@ const Provider = require('./Provider')
  */
 async function fetchKeys (url, providerName, credentialRequestParams) {
   try {
-    const { credentials } = await got.post(url, {
+    const { credentials } = await (await got).post(url, {
       json: { provider: providerName, parameters: credentialRequestParams },
     }).json()
 

+ 8 - 9
packages/@uppy/companion/src/server/provider/drive/index.js

@@ -1,5 +1,3 @@
-const got = require('got').default
-
 const Provider = require('../Provider')
 const logger = require('../../logger')
 const { VIRTUAL_SHARED_DIR, adaptData, isShortcut, isGsuiteFile, getGsuiteExportType } = require('./adapter')
@@ -8,6 +6,7 @@ const { prepareStream } = require('../../helpers/utils')
 const { MAX_AGE_REFRESH_TOKEN } = require('../../helpers/jwt')
 const { ProviderAuthError } = require('../error')
 
+const got = require('../../got')
 
 // For testing refresh token:
 // first run a download with mockAccessTokenExpiredError = true 
@@ -22,19 +21,19 @@ const DRIVE_FILES_FIELDS = `kind,nextPageToken,incompleteSearch,files(${DRIVE_FI
 // using wildcard to get all 'drive' fields because specifying fields seems no to work for the /drives endpoint
 const SHARED_DRIVE_FIELDS = '*'
 
-const getClient = ({ token }) => got.extend({
+const getClient = async ({ token }) => (await got).extend({
   prefixUrl: 'https://www.googleapis.com/drive/v3',
   headers: {
     authorization: `Bearer ${token}`,
   },
 })
 
-const getOauthClient = () => got.extend({
+const getOauthClient = async () => (await got).extend({
   prefixUrl: 'https://oauth2.googleapis.com',
 })
 
 async function getStats ({ id, token }) {
-  const client = getClient({ token })
+  const client = await getClient({ token })
 
   const getStatsInner = async (statsOfId) => (
     client.get(`files/${encodeURIComponent(statsOfId)}`, { searchParams: { fields: DRIVE_FILE_FIELDS, supportsAllDrives: true }, responseType: 'json' }).json()
@@ -68,7 +67,7 @@ class Drive extends Provider {
       const isRoot = directory === 'root'
       const isVirtualSharedDirRoot = directory === VIRTUAL_SHARED_DIR
 
-      const client = getClient({ token })
+      const client = await getClient({ token })
 
       async function fetchSharedDrives (pageToken = null) {
         const shouldListSharedDrives = isRoot && !query.cursor
@@ -137,7 +136,7 @@ class Drive extends Provider {
     }
 
     return this.#withErrorHandling('provider.drive.download.error', async () => {
-      const client = getClient({ token })
+      const client = await getClient({ token })
 
       const { mimeType, id } = await getStats({ id: idIn, token })
 
@@ -179,7 +178,7 @@ class Drive extends Provider {
 
   logout ({ token }) {
     return this.#withErrorHandling('provider.drive.logout.error', async () => {
-      await got.post('https://accounts.google.com/o/oauth2/revoke', {
+      await (await got).post('https://accounts.google.com/o/oauth2/revoke', {
         searchParams: { token },
         responseType: 'json',
       })
@@ -190,7 +189,7 @@ class Drive extends Provider {
 
   async refreshToken ({ clientId, clientSecret, refreshToken }) {
     return this.#withErrorHandling('provider.drive.token.refresh.error', async () => {
-      const { access_token: accessToken } = await getOauthClient().post('token', { responseType: 'json', form: { refresh_token: refreshToken, grant_type: 'refresh_token', client_id: clientId, client_secret: clientSecret } }).json()
+      const { access_token: accessToken } = await (await getOauthClient()).post('token', { responseType: 'json', form: { refresh_token: refreshToken, grant_type: 'refresh_token', client_id: clientId, client_secret: clientSecret } }).json()
       return { accessToken }
     })
   }

+ 13 - 12
packages/@uppy/companion/src/server/provider/dropbox/index.js

@@ -1,11 +1,11 @@
-const got = require('got').default
-
 const Provider = require('../Provider')
 const adaptData = require('./adapter')
 const { withProviderErrorHandling } = require('../providerErrors')
 const { prepareStream } = require('../../helpers/utils')
 const { MAX_AGE_REFRESH_TOKEN } = require('../../helpers/jwt')
 
+const got = require('../../got')
+
 // From https://www.dropbox.com/developers/reference/json-encoding:
 //
 // This function is simple and has OK performance compared to more
@@ -18,23 +18,24 @@ function httpHeaderSafeJson (v) {
     })
 }
 
-const getClient = ({ token }) => got.extend({
+const getClient = async ({ token }) => (await got).extend({
   prefixUrl: 'https://api.dropboxapi.com/2',
   headers: {
     authorization: `Bearer ${token}`,
   },
 })
 
-const getOauthClient = () => got.extend({
+const getOauthClient = async () => (await got).extend({
   prefixUrl: 'https://api.dropboxapi.com/oauth2',
 })
 
 async function list ({ directory, query, token }) {
+  const client = await getClient({ token })
   if (query.cursor) {
-    return getClient({ token }).post('files/list_folder/continue', { json: { cursor: query.cursor }, responseType: 'json' }).json()
+    return client.post('files/list_folder/continue', { json: { cursor: query.cursor }, responseType: 'json' }).json()
   }
 
-  return getClient({ token }).post('files/list_folder', {
+  return client.post('files/list_folder', {
     searchParams: query,
     json: {
       path: `${directory || ''}`,
@@ -47,7 +48,7 @@ async function list ({ directory, query, token }) {
 }
 
 async function userInfo ({ token }) {
-  return getClient({ token }).post('users/get_current_account', { responseType: 'json' }).json()
+  return (await getClient({ token })).post('users/get_current_account', { responseType: 'json' }).json()
 }
 
 /**
@@ -85,7 +86,7 @@ class DropBox extends Provider {
 
   async download ({ id, token }) {
     return this.#withErrorHandling('provider.dropbox.download.error', async () => {
-      const stream = getClient({ token }).stream.post('files/download', {
+      const stream = (await getClient({ token })).stream.post('files/download', {
         prefixUrl: 'https://content.dropboxapi.com/2',
         headers: {
           'Dropbox-API-Arg': httpHeaderSafeJson({ path: String(id) }),
@@ -102,7 +103,7 @@ class DropBox extends Provider {
 
   async thumbnail ({ id, token }) {
     return this.#withErrorHandling('provider.dropbox.thumbnail.error', async () => {
-      const stream = getClient({ token }).stream.post('files/get_thumbnail_v2', {
+      const stream = (await getClient({ token })).stream.post('files/get_thumbnail_v2', {
         prefixUrl: 'https://content.dropboxapi.com/2',
         headers: { 'Dropbox-API-Arg': httpHeaderSafeJson({ resource: { '.tag': 'path', path: `${id}` }, size: 'w256h256', format: 'jpeg' }) },
         body: Buffer.alloc(0),
@@ -116,21 +117,21 @@ class DropBox extends Provider {
 
   async size ({ id, token }) {
     return this.#withErrorHandling('provider.dropbox.size.error', async () => {
-      const { size } = await getClient({ token }).post('files/get_metadata', { json: { path: id }, responseType: 'json' }).json()
+      const { size } = await (await getClient({ token })).post('files/get_metadata', { json: { path: id }, responseType: 'json' }).json()
       return parseInt(size, 10)
     })
   }
 
   async logout ({ token }) {
     return this.#withErrorHandling('provider.dropbox.logout.error', async () => {
-      await getClient({ token }).post('auth/token/revoke', { responseType: 'json' })
+      await (await getClient({ token })).post('auth/token/revoke', { responseType: 'json' })
       return { revoked: true }
     })
   }
 
   async refreshToken ({ clientId, clientSecret, refreshToken }) {
     return this.#withErrorHandling('provider.dropbox.token.refresh.error', async () => {
-      const { access_token: accessToken } = await getOauthClient().post('token', { form: { refresh_token: refreshToken, grant_type: 'refresh_token', client_id: clientId, client_secret: clientSecret } }).json()
+      const { access_token: accessToken } = await (await getOauthClient()).post('token', { form: { refresh_token: refreshToken, grant_type: 'refresh_token', client_id: clientId, client_secret: clientSecret } }).json()
       return { accessToken }
     })
   }

+ 7 - 7
packages/@uppy/companion/src/server/provider/facebook/index.js

@@ -1,5 +1,3 @@
-const got = require('got').default
-
 const Provider = require('../Provider')
 const { getURLMeta } = require('../../helpers/request')
 const logger = require('../../logger')
@@ -7,7 +5,9 @@ const { adaptData, sortImages } = require('./adapter')
 const { withProviderErrorHandling } = require('../providerErrors')
 const { prepareStream } = require('../../helpers/utils')
 
-const getClient = ({ token }) => got.extend({
+const got = require('../../got')
+
+const getClient = async ({ token }) => (await got).extend({
   prefixUrl: 'https://graph.facebook.com',
   headers: {
     authorization: `Bearer ${token}`,
@@ -15,7 +15,7 @@ const getClient = ({ token }) => got.extend({
 })
 
 async function getMediaUrl ({ token, id }) {
-  const body = await getClient({ token }).get(String(id), { searchParams: { fields: 'images' }, responseType: 'json' }).json()
+  const body = await (await getClient({ token })).get(String(id), { searchParams: { fields: 'images' }, responseType: 'json' }).json()
   const sortedImages = sortImages(body.images)
   return sortedImages[sortedImages.length - 1].source
 }
@@ -40,7 +40,7 @@ class Facebook extends Provider {
         qs.fields = 'icon,images,name,width,height,created_time'
       }
 
-      const client = getClient({ token })
+      const client = await getClient({ token })
 
       const [{ email }, list] = await Promise.all([
         client.get('me', { searchParams: { fields: 'email' }, responseType: 'json' }).json(),
@@ -53,7 +53,7 @@ class Facebook extends Provider {
   async download ({ id, token }) {
     return this.#withErrorHandling('provider.facebook.download.error', async () => {
       const url = await getMediaUrl({ token, id })
-      const stream = got.stream.get(url, { responseType: 'json' })
+      const stream = (await got).stream.get(url, { responseType: 'json' })
       await prepareStream(stream)
       return { stream }
     })
@@ -76,7 +76,7 @@ class Facebook extends Provider {
 
   async logout ({ token }) {
     return this.#withErrorHandling('provider.facebook.logout.error', async () => {
-      await getClient({ token }).delete('me/permissions', { responseType: 'json' }).json()
+      await (await getClient({ token })).delete('me/permissions', { responseType: 'json' }).json()
       return { revoked: true }
     })
   }

+ 6 - 6
packages/@uppy/companion/src/server/provider/instagram/graph/index.js

@@ -1,5 +1,3 @@
-const got = require('got').default
-
 const Provider = require('../../Provider')
 const { getURLMeta } = require('../../../helpers/request')
 const logger = require('../../../logger')
@@ -7,7 +5,9 @@ const adaptData = require('./adapter')
 const { withProviderErrorHandling } = require('../../providerErrors')
 const { prepareStream } = require('../../../helpers/utils')
 
-const getClient = ({ token }) => got.extend({
+const got = require('../../../got')
+
+const getClient = async ({ token }) => (await got).extend({
   prefixUrl: 'https://graph.instagram.com',
   headers: {
     authorization: `Bearer ${token}`,
@@ -15,7 +15,7 @@ const getClient = ({ token }) => got.extend({
 })
 
 async function getMediaUrl ({ token, id }) {
-  const body = await getClient({ token }).get(String(id), { searchParams: { fields: 'media_url' }, responseType: 'json' }).json()
+  const body = await (await getClient({ token })).get(String(id), { searchParams: { fields: 'media_url' }, responseType: 'json' }).json()
   return body.media_url
 }
 
@@ -41,7 +41,7 @@ class Instagram extends Provider {
 
       if (query.cursor) qs.after = query.cursor
 
-      const client = getClient({ token })
+      const client = await getClient({ token })
 
       const [{ username }, list] = await Promise.all([
         client.get('me', { searchParams: { fields: 'username' }, responseType: 'json' }).json(),
@@ -54,7 +54,7 @@ class Instagram extends Provider {
   async download ({ id, token }) {
     return this.#withErrorHandling('provider.instagram.download.error', async () => {
       const url = await getMediaUrl({ token, id })
-      const stream = got.stream.get(url, { responseType: 'json' })
+      const stream = (await got).stream.get(url, { responseType: 'json' })
       await prepareStream(stream)
       return { stream }
     })

+ 8 - 8
packages/@uppy/companion/src/server/provider/onedrive/index.js

@@ -1,19 +1,19 @@
-const got = require('got').default
-
 const Provider = require('../Provider')
 const logger = require('../../logger')
 const adaptData = require('./adapter')
 const { withProviderErrorHandling } = require('../providerErrors')
 const { prepareStream } = require('../../helpers/utils')
 
-const getClient = ({ token }) => got.extend({
+const got = require('../../got')
+
+const getClient = async ({ token }) => (await got).extend({
   prefixUrl: 'https://graph.microsoft.com/v1.0',
   headers: {
     authorization: `Bearer ${token}`,
   },
 })
 
-const getOauthClient = () => got.extend({
+const getOauthClient = async () => (await got).extend({
   prefixUrl: 'https://login.live.com',
 })
 
@@ -47,7 +47,7 @@ class OneDrive extends Provider {
         qs.$skiptoken = query.cursor
       }
 
-      const client = getClient({ token })
+      const client = await getClient({ token })
 
       const [{ mail, userPrincipalName }, list] = await Promise.all([
         client.get('me', { responseType: 'json' }).json(),
@@ -60,7 +60,7 @@ class OneDrive extends Provider {
 
   async download ({ id, token, query }) {
     return this.#withErrorHandling('provider.onedrive.download.error', async () => {
-      const stream = getClient({ token }).stream.get(`${getRootPath(query)}/items/${id}/content`, { responseType: 'json' })
+      const stream = (await getClient({ token })).stream.get(`${getRootPath(query)}/items/${id}/content`, { responseType: 'json' })
       await prepareStream(stream)
       return { stream }
     })
@@ -75,7 +75,7 @@ class OneDrive extends Provider {
 
   async size ({ id, query, token }) {
     return this.#withErrorHandling('provider.onedrive.size.error', async () => {
-      const { size } = await getClient({ token }).get(`${getRootPath(query)}/items/${id}`, { responseType: 'json' }).json()
+      const { size } = await (await getClient({ token })).get(`${getRootPath(query)}/items/${id}`, { responseType: 'json' }).json()
       return size
     })
   }
@@ -88,7 +88,7 @@ class OneDrive extends Provider {
 
   async refreshToken ({ clientId, clientSecret, refreshToken, redirectUri }) {
     return this.#withErrorHandling('provider.onedrive.token.refresh.error', async () => {
-      const { access_token: accessToken } = await getOauthClient().post('oauth20_token.srf', { responseType: 'json', form: { refresh_token: refreshToken, grant_type: 'refresh_token', client_id: clientId, client_secret: clientSecret, redirect_uri: redirectUri } }).json()
+      const { access_token: accessToken } = await (await getOauthClient()).post('oauth20_token.srf', { responseType: 'json', form: { refresh_token: refreshToken, grant_type: 'refresh_token', client_id: clientId, client_secret: clientSecret, redirect_uri: redirectUri } }).json()
       return { accessToken }
     })
   }

+ 7 - 7
packages/@uppy/companion/src/server/provider/unsplash/index.js

@@ -1,5 +1,3 @@
-const got = require('got').default
-
 const Provider = require('../Provider')
 const { getURLMeta } = require('../../helpers/request')
 const adaptData = require('./adapter')
@@ -7,9 +5,11 @@ const { withProviderErrorHandling } = require('../providerErrors')
 const { prepareStream } = require('../../helpers/utils')
 const { ProviderApiError } = require('../error')
 
+const got = require('../../got')
+
 const BASE_URL = 'https://api.unsplash.com'
 
-const getClient = ({ token }) => got.extend({
+const getClient = async ({ token }) => (await got).extend({
   prefixUrl: BASE_URL,
   headers: {
     authorization: `Client-ID ${token}`,
@@ -31,18 +31,18 @@ class Unsplash extends Provider {
       const qs = { per_page: 40, query: query.q }
       if (query.cursor) qs.page = query.cursor
 
-      const response = await getClient({ token }).get('search/photos', { searchParams: qs, responseType: 'json' }).json()
+      const response = await (await getClient({ token })).get('search/photos', { searchParams: qs, responseType: 'json' }).json()
       return adaptData(response, query)
     })
   }
 
   async download ({ id, token }) {
     return this.#withErrorHandling('provider.unsplash.download.error', async () => {
-      const client = getClient({ token })
+      const client = await getClient({ token })
 
       const { links: { download: url, download_location: attributionUrl } } = await getPhotoMeta(client, id)
 
-      const stream = got.stream.get(url, { responseType: 'json' })
+      const stream = (await got).stream.get(url, { responseType: 'json' })
       await prepareStream(stream)
 
       // To attribute the author of the image, we call the `download_location`
@@ -57,7 +57,7 @@ class Unsplash extends Provider {
 
   async size ({ id, token }) {
     return this.#withErrorHandling('provider.unsplash.size.error', async () => {
-      const { links: { download: url } } = await getPhotoMeta(getClient({ token }), id)
+      const { links: { download: url } } = await getPhotoMeta(await getClient({ token }), id)
       const { size } = await getURLMeta(url, true)
       return size
     })

+ 8 - 7
packages/@uppy/companion/src/server/provider/zoom/index.js

@@ -1,4 +1,3 @@
-const got = require('got').default
 const moment = require('moment-timezone')
 
 const Provider = require('../Provider')
@@ -6,11 +5,13 @@ const { initializeData, adaptData } = require('./adapter')
 const { withProviderErrorHandling } = require('../providerErrors')
 const { prepareStream, getBasicAuthHeader } = require('../../helpers/utils')
 
+const got = require('../../got')
+
 const BASE_URL = 'https://zoom.us/v2'
 const PAGE_SIZE = 300
 const DEAUTH_EVENT_NAME = 'app_deauthorized'
 
-const getClient = ({ token }) => got.extend({
+const getClient = async ({ token }) => (await got).extend({
   prefixUrl: BASE_URL,
   headers: {
     authorization: `Bearer ${token}`,
@@ -44,7 +45,7 @@ class Zoom extends Provider {
       const { cursor, from, to } = query
       const meetingId = options.directory || ''
 
-      const client = getClient({ token })
+      const client = await getClient({ token })
       const user = await client.get('users/me', { responseType: 'json' }).json()
 
       const { timezone } = user
@@ -86,7 +87,7 @@ class Zoom extends Provider {
       // cc files don't have an ID or size
       const { recordingStart, recordingId: fileId } = query
 
-      const client = getClient({ token })
+      const client = await getClient({ token })
 
       const foundFile = await findFile({ client, meetingId, fileId, recordingStart })
       const url = foundFile?.download_url
@@ -100,7 +101,7 @@ class Zoom extends Provider {
 
   async size ({ id: meetingId, token, query }) {
     return this.#withErrorHandling('provider.zoom.size.error', async () => {
-      const client = getClient({ token })
+      const client = await getClient({ token })
       const { recordingStart, recordingId: fileId } = query
 
       const foundFile = await findFile({ client, meetingId, fileId, recordingStart })
@@ -113,7 +114,7 @@ class Zoom extends Provider {
     return this.#withErrorHandling('provider.zoom.logout.error', async () => {
       const { key, secret } = await companion.getProviderCredentials()
 
-      const { status } = await got.post('https://zoom.us/oauth/revoke', {
+      const { status } = await (await got).post('https://zoom.us/oauth/revoke', {
         searchParams: { token },
         headers: { Authorization: getBasicAuthHeader(key, secret) },
         responseType: 'json',
@@ -136,7 +137,7 @@ class Zoom extends Provider {
         return { data: {}, status: 400 }
       }
 
-      await got.post('https://api.zoom.us/oauth/data/compliance', {
+      await (await got).post('https://api.zoom.us/oauth/data/compliance', {
         headers: { Authorization: getBasicAuthHeader(key, secret) },
         json: {
           client_id: key,

+ 6 - 6
packages/@uppy/companion/test/__tests__/http-agent.js

@@ -11,24 +11,24 @@ describe('test protected request Agent', () => {
   test('allows URLs without IP addresses', async () => {
     nock('https://transloadit.com').get('/').reply(200)
     const url = 'https://transloadit.com'
-    await getProtectedGot({ blockLocalIPs: true }).get(url)
+    return (await getProtectedGot({ blockLocalIPs: true })).get(url)
   })
 
   test('blocks url that resolves to forbidden IP', async () => {
     const url = 'https://localhost'
-    const promise = getProtectedGot({ blockLocalIPs: true }).get(url)
+    const promise = getProtectedGot({ blockLocalIPs: true }).then(got => got.get(url))
     await expect(promise).rejects.toThrow(/^Forbidden resolved IP address/)
   })
 
   test('blocks private http IP address', async () => {
     const url = 'http://172.20.10.4:8090'
-    const promise = getProtectedGot({ blockLocalIPs: true }).get(url)
+    const promise = getProtectedGot({ blockLocalIPs: true }).then(got => got.get(url))
     await expect(promise).rejects.toThrow(new Error(FORBIDDEN_IP_ADDRESS))
   })
 
   test('blocks private https IP address', async () => {
     const url = 'https://172.20.10.4:8090'
-    const promise = getProtectedGot({ blockLocalIPs: true }).get(url)
+    const promise = getProtectedGot({ blockLocalIPs: true }).then(got => got.get(url))
     await expect(promise).rejects.toThrow(new Error(FORBIDDEN_IP_ADDRESS))
   })
 
@@ -57,12 +57,12 @@ describe('test protected request Agent', () => {
 
     for (const ip of ipv4s) {
       const url = `http://${ip}:8090`
-      const promise = getProtectedGot({ blockLocalIPs: true }).get(url)
+      const promise = getProtectedGot({ blockLocalIPs: true }).then(got => got.get(url))
       await expect(promise).rejects.toThrow(new Error(FORBIDDEN_IP_ADDRESS))
     }
     for (const ip of ipv6s) {
       const url = `http://[${ip}]:8090`
-      const promise = getProtectedGot({ blockLocalIPs: true }).get(url)
+      const promise = getProtectedGot({ blockLocalIPs: true }).then(got => got.get(url))
       await expect(promise).rejects.toThrow(new Error(FORBIDDEN_IP_ADDRESS))
     }
   })

+ 32 - 16
packages/@uppy/companion/test/__tests__/uploader.js

@@ -2,8 +2,10 @@
 
 jest.mock('tus-js-client')
 
-const intoStream = require('into-stream')
+const { Readable } = require('node:stream')
 const fs = require('node:fs')
+const { createServer } = require('node:http')
+const { once } = require('node:events')
 const nock = require('nock')
 
 const Uploader = require('../../src/server/Uploader')
@@ -52,7 +54,7 @@ describe('uploader with tus protocol', () => {
 
   test('upload functions with tus protocol', async () => {
     const fileContent = Buffer.from('Some file content')
-    const stream = intoStream(fileContent)
+    const stream = Readable.from([fileContent])
     const opts = {
       companionOptions,
       endpoint: 'http://url.myendpoint.com/files',
@@ -108,7 +110,7 @@ describe('uploader with tus protocol', () => {
 
   test('upload functions with tus protocol without size', async () => {
     const fileContent = Buffer.alloc(1e6)
-    const stream = intoStream(fileContent)
+    const stream = Readable.from([fileContent])
     const opts = {
       companionOptions,
       endpoint: 'http://url.myendpoint.com/files',
@@ -153,7 +155,7 @@ describe('uploader with tus protocol', () => {
       })
       socketClient.onUploadSuccess(uploadToken, (message) => {
         try {
-          expect(firstReceivedProgress.bytesUploaded).toBe(8192)
+          expect(firstReceivedProgress.bytesUploaded).toBe(500_000)
 
           // see __mocks__/tus-js-client.js
           expect(message.payload.url).toBe('https://tus.endpoint/files/foo-bar')
@@ -164,13 +166,13 @@ describe('uploader with tus protocol', () => {
     })
   })
 
-  async function runMultipartTest ({ metadata, useFormData, includeSize = true  } = {}) {
+  async function runMultipartTest ({ metadata, useFormData, includeSize = true, address = 'localhost'  } = {}) {
     const fileContent = Buffer.from('Some file content')
-    const stream = intoStream(fileContent)
+    const stream = Readable.from([fileContent])
 
     const opts = {
       companionOptions,
-      endpoint: 'http://localhost',
+      endpoint: `http://${address}`,
       protocol: 'multipart',
       size: includeSize ? fileContent.length : undefined,
       metadata,
@@ -183,14 +185,30 @@ describe('uploader with tus protocol', () => {
   }
 
   test('upload functions with xhr protocol', async () => {
-    nock('http://localhost').post('/').reply(200)
-
-    const ret = await runMultipartTest()
-    expect(ret).toMatchObject({ url: null, extraData: { response: expect.anything(), bytesUploaded: 17 } })
+    let alreadyCalled = false
+    // We are creating our own test server for this test
+    // instead of using nock because of a bug when passing a Node.js stream to got.
+    // Ref: https://github.com/nock/nock/issues/2595
+    const server = createServer((req,res) => {
+      if (alreadyCalled) throw new Error('already called')
+      alreadyCalled = true
+      if (req.url === '/' && req.method === 'POST') {
+        res.writeHead(200)
+        res.end('OK')
+      }
+    }).listen()
+    try {
+      await once(server, 'listening')
+      
+      const ret = await runMultipartTest({ address: `localhost:${server.address().port}` })
+      expect(ret).toMatchObject({ url: null, extraData: { response: expect.anything(), bytesUploaded: 17 } })
+    } finally {
+      server.close()
+    }
   })
 
   // eslint-disable-next-line max-len
-  const formDataNoMetaMatch = /^----------------------------\d+\r\nContent-Disposition: form-data; name="files\[\]"; filename="uppy-file-[^"]+"\r\nContent-Type: application\/octet-stream\r\n\r\nSome file content\r\n----------------------------\d+--\r\n$/
+  const formDataNoMetaMatch = /^--form-data-boundary-[a-z0-9]+\r\nContent-Disposition: form-data; name="files\[\]"; filename="uppy-file-[^"]+"\r\nContent-Type: application\/octet-stream\r\n\r\nSome file content\r\n--form-data-boundary-[a-z0-9]+--\r\n\r\n$/
 
   test('upload functions with xhr formdata', async () => {
     nock('http://localhost').post('/', formDataNoMetaMatch)
@@ -201,7 +219,6 @@ describe('uploader with tus protocol', () => {
   })
 
   test('upload functions with unknown file size', async () => {
-    // eslint-disable-next-line max-len
     nock('http://localhost').post('/', formDataNoMetaMatch)
       .reply(200)
 
@@ -211,8 +228,7 @@ describe('uploader with tus protocol', () => {
 
   // https://github.com/transloadit/uppy/issues/3477
   test('upload functions with xhr formdata and metadata', async () => {
-    // eslint-disable-next-line max-len
-    nock('http://localhost').post('/', /^----------------------------\d+\r\nContent-Disposition: form-data; name="key1"\r\n\r\nnull\r\n----------------------------\d+\r\nContent-Disposition: form-data; name="key2"\r\n\r\ntrue\r\n----------------------------\d+\r\nContent-Disposition: form-data; name="key3"\r\n\r\n\d+\r\n----------------------------\d+\r\nContent-Disposition: form-data; name="key4"\r\n\r\n\[object Object\]\r\n----------------------------\d+\r\nContent-Disposition: form-data; name="key5"\r\n\r\n\(\) => {}\r\n----------------------------\d+\r\nContent-Disposition: form-data; name="key6"\r\n\r\nSymbol\(\)\r\n----------------------------\d+\r\nContent-Disposition: form-data; name="files\[\]"; filename="uppy-file-[^"]+"\r\nContent-Type: application\/octet-stream\r\n\r\nSome file content\r\n----------------------------\d+--\r\n$/)
+    nock('http://localhost').post('/', /^--form-data-boundary-[a-z0-9]+\r\nContent-Disposition: form-data; name="key1"\r\n\r\nnull\r\n--form-data-boundary-[a-z0-9]+\r\nContent-Disposition: form-data; name="key2"\r\n\r\ntrue\r\n--form-data-boundary-[a-z0-9]+\r\nContent-Disposition: form-data; name="key3"\r\n\r\n\d+\r\n--form-data-boundary-[a-z0-9]+\r\nContent-Disposition: form-data; name="key4"\r\n\r\n\[object Object\]\r\n--form-data-boundary-[a-z0-9]+\r\nContent-Disposition: form-data; name="key5"\r\n\r\n\(\) => \{\}\r\n--form-data-boundary-[a-z0-9]+\r\nContent-Disposition: form-data; name="key6"\r\n\r\nSymbol\(\)\r\n--form-data-boundary-[a-z0-9]+\r\nContent-Disposition: form-data; name="files\[\]"; filename="uppy-file-[^"]+"\r\nContent-Type: application\/octet-stream\r\n\r\nSome file content\r\n--form-data-boundary-[a-z0-9]+--\r\n\r\n$/)
       .reply(200)
 
     const metadata = {
@@ -257,7 +273,7 @@ describe('uploader with tus protocol', () => {
 
   test('uploader respects maxFileSize with unknown size', async () => {
     const fileContent = Buffer.alloc(10000)
-    const stream = intoStream(fileContent)
+    const stream = Readable.from([fileContent])
     const opts = {
       companionOptions: { ...companionOptions, maxFileSize: 1000 },
       endpoint: 'http://url.myendpoint.com/files',

+ 1 - 1
packages/@uppy/companion/tsconfig.json

@@ -4,7 +4,7 @@
     "module": "Node16",
     "moduleResolution": "node16",
     "declaration": true,
-    "target": "es2019",
+    "target": "es2022",
     "noImplicitAny": false,
     "sourceMap": false,
     "allowJs": true,

Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 311 - 293
yarn.lock


Một số tệp đã không được hiển thị bởi vì quá nhiều tập tin thay đổi trong này khác