Browse Source

companion: support new Instagram Graph API

ifedapoolarewaju 5 years ago
parent
commit
b937f4893e

+ 16 - 18
package-lock.json

@@ -6125,7 +6125,7 @@
         "express-prom-bundle": "3.3.0",
         "express-request-id": "1.4.1",
         "express-session": "1.15.6",
-        "grant-express": "4.1.2",
+        "grant": "github:ifedapoolarewaju/grant#ba442905a89c5f2041b6a9101d4be8ae15c20458",
         "helmet": "3.8.2",
         "isobject": "3.0.1",
         "jsonwebtoken": "8.3.0",
@@ -6224,6 +6224,14 @@
             "utils-merge": "1.0.1"
           }
         },
+        "grant-express": {
+          "version": "4.6.4",
+          "resolved": "https://registry.npmjs.org/grant-express/-/grant-express-4.6.4.tgz",
+          "integrity": "sha512-7pvLC7EWU5f6Vl+kq1ZBtt+cNayzTonsgvpOGpBXxl2P1xIbvamQKKElGLwunCp5iVn5N6T5N6vFSxX9YdnTEw==",
+          "requires": {
+            "grant": "4.6.4"
+          }
+        },
         "http-errors": {
           "version": "1.6.3",
           "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz",
@@ -17425,24 +17433,14 @@
       "integrity": "sha512-jpSvDPV4Cq/bgtpndIWbI5hmYxhQGHPC4d4cqBPb4DLniCfhJokdXhwhaDuLBGLQdvvRum/UiX6ECVIPvDXqdg=="
     },
     "grant": {
-      "version": "4.1.2",
-      "resolved": "https://registry.npmjs.org/grant/-/grant-4.1.2.tgz",
-      "integrity": "sha512-J+Cb0m8vDYU3tvcA47AbcD4KMioWeN6RDPxnPbcqpqAuHrn/J61YZZO4nx2B1qDQEhSahW3NwQCM1PZov4uMyw==",
+      "version": "github:ifedapoolarewaju/grant#ba442905a89c5f2041b6a9101d4be8ae15c20458",
+      "from": "github:ifedapoolarewaju/grant#ba442905a89c5f2041b6a9101d4be8ae15c20458",
       "requires": {
-        "deep-copy": "^1.4.2",
-        "qs": "^6.5.1",
-        "request-compose": "0.0.19",
+        "qs": "^6.7.0",
+        "request-compose": "^1.2.0",
         "request-oauth": "0.0.3"
       }
     },
-    "grant-express": {
-      "version": "4.1.2",
-      "resolved": "https://registry.npmjs.org/grant-express/-/grant-express-4.1.2.tgz",
-      "integrity": "sha512-q90pj9XM5tUNAVkTIy/N73vK9rc+SiW4J5CUG/z30yL29PM5Yzpt1kORBoqsAmygTSUH3L/ZupXg1Dng3SXM1g==",
-      "requires": {
-        "grant": "4.1.2"
-      }
-    },
     "grapheme-breaker": {
       "version": "0.3.2",
       "resolved": "https://registry.npmjs.org/grapheme-breaker/-/grapheme-breaker-0.3.2.tgz",
@@ -28087,9 +28085,9 @@
       }
     },
     "request-compose": {
-      "version": "0.0.19",
-      "resolved": "https://registry.npmjs.org/request-compose/-/request-compose-0.0.19.tgz",
-      "integrity": "sha512-BBMilZ4uReMzOCXvysw6l8nT5WwQDo/H8vaYNQ4BXPa7/0OJIaJskk4Ozzfpd2bmLb5ZQLpxJf/FMCeTX5QrkQ=="
+      "version": "1.2.1",
+      "resolved": "https://registry.npmjs.org/request-compose/-/request-compose-1.2.1.tgz",
+      "integrity": "sha512-w4qjUH1N4OdMfnHVi4Z0oKvDZyu75rJlvnuKe40wVg+khnfdJLt0qf+LF8QjIiDqSOSYdbMZE6a0ixU58B3Jow=="
     },
     "request-oauth": {
       "version": "0.0.3",

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

@@ -43,7 +43,7 @@
     "express-prom-bundle": "3.3.0",
     "express-request-id": "1.4.1",
     "express-session": "1.15.6",
-    "grant-express": "4.1.2",
+    "grant": "github:ifedapoolarewaju/grant#ba442905a89c5f2041b6a9101d4be8ae15c20458",
     "helmet": "3.8.2",
     "isobject": "3.0.1",
     "jsonwebtoken": "8.3.0",

+ 3 - 3
packages/@uppy/companion/src/companion.js

@@ -1,6 +1,6 @@
 const express = require('express')
 // @ts-ignore
-const Grant = require('grant-express')
+const Grant = require('grant').express()
 const grantConfig = require('./config/grant')()
 const providerManager = require('./server/provider')
 const controllers = require('./server/controllers')
@@ -19,7 +19,6 @@ const { STORAGE_PREFIX } = require('./server/Uploader')
 const middlewares = require('./server/middlewares')
 const { shortenToken } = require('./server/Uploader')
 
-const providers = providerManager.getDefaultProviders()
 const defaultOptions = {
   server: {
     protocol: 'http',
@@ -44,6 +43,7 @@ const defaultOptions = {
  */
 module.exports.app = (options = {}) => {
   options = merge({}, defaultOptions, options)
+  const providers = providerManager.getDefaultProviders(options)
   providerManager.addProviderOptions(options, grantConfig)
 
   const customProviders = options.customProviders
@@ -61,7 +61,7 @@ module.exports.app = (options = {}) => {
   app.use(cookieParser()) // server tokens are added to cookies
 
   app.use(interceptGrantErrorResponse)
-  app.use(new Grant(grantConfig))
+  app.use(Grant(grantConfig))
   app.use((req, res, next) => {
     res.header('Access-Control-Allow-Methods', 'GET, POST, OPTIONS, DELETE')
     res.header(

+ 2 - 1
packages/@uppy/companion/src/server/controllers/send-token.js

@@ -19,7 +19,8 @@ module.exports = function sendToken (req, res, next) {
   // add the token to cookies for thumbnail/image requests
   tokenService.addToCookies(res, uppyAuthToken, req.companion.options, req.companion.provider.authProvider)
 
-  const state = (req.session.grant || {}).state
+  const dynamic = (req.session.grant || {}).dynamic || {}
+  const state = dynamic.state
   if (state) {
     const origin = oAuthState.getFromState(state, 'origin', req.companion.options.secret)
     const clientVersion = oAuthState.getFromState(

+ 64 - 0
packages/@uppy/companion/src/server/provider/Provider.js

@@ -0,0 +1,64 @@
+/**
+ * Provider interface defines the specifications of any provider implementation
+ */
+class Provider {
+  /**
+     *
+     * @param {object} options
+     */
+  constructor (options) {
+    return this
+  }
+
+  /**
+     * config to extend the grant config
+     */
+  static getExtraConfig () {
+    return {}
+  }
+
+  /**
+     * list the files and folders in the provider account
+     * @param {object} options
+     * @param {function} cb
+     */
+  list (options, cb) {
+    throw new Error('method not implemented')
+  }
+
+  /**
+     * download a certain file from the provider account
+     * @param {object} options
+     * @param {function} cb
+     */
+  download (options, cb) {
+    throw new Error('method not implemented')
+  }
+
+  /**
+     * return a thumbnail for a provider file
+     * @param {object} options
+     * @param {function} cb
+     */
+  thumbnail (options, cb) {
+    throw new Error('method not implemented')
+  }
+
+  /**
+     * get the size of a certain file in the provider account
+     * @param {object} options
+     * @param {function} cb
+     */
+  size (options, cb) {
+    throw new Error('method not implemented')
+  }
+
+  /**
+     * @returns {string}
+     */
+  static get authProvider () {
+    return ''
+  }
+}
+
+module.exports = Provider

+ 4 - 1
packages/@uppy/companion/src/server/provider/drive/index.js

@@ -1,3 +1,5 @@
+const Provider = require('../Provider')
+
 const request = require('request')
 // @ts-ignore
 const purest = require('purest')({ request })
@@ -9,8 +11,9 @@ 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 = '*'
 
-class Drive {
+class Drive extends Provider {
   constructor (options) {
+    super(options)
     this.authProvider = options.provider = Drive.authProvider
     options.alias = 'drive'
     options.version = 'v3'

+ 4 - 1
packages/@uppy/companion/src/server/provider/dropbox/index.js

@@ -1,3 +1,5 @@
+const Provider = require('../Provider')
+
 const request = require('request')
 const purest = require('purest')({ request })
 const logger = require('../../logger')
@@ -17,8 +19,9 @@ function httpHeaderSafeJson (v) {
   )
 }
 
-class DropBox {
+class DropBox extends Provider {
   constructor (options) {
+    super(options)
     this.authProvider = options.provider = DropBox.authProvider
     this.client = purest(options)
   }

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

@@ -1,3 +1,5 @@
+const Provider = require('../Provider')
+
 const request = require('request')
 const purest = require('purest')({ request })
 const utils = require('../../helpers/utils')
@@ -5,8 +7,9 @@ const logger = require('../../logger')
 const adapter = require('./adapter')
 const AuthError = require('../error')
 
-class Facebook {
+class Facebook extends Provider {
   constructor (options) {
+    super(options)
     this.authProvider = options.provider = Facebook.authProvider
     this.client = purest(options)
   }

+ 27 - 63
packages/@uppy/companion/src/server/provider/index.js

@@ -6,62 +6,13 @@ const config = require('@purest/providers')
 const dropbox = require('./dropbox')
 const drive = require('./drive')
 const instagram = require('./instagram')
+const instagramGraph = require('./instagram/graph')
 const facebook = require('./facebook')
 const onedrive = require('./onedrive')
 const { getURLBuilder } = require('../helpers/utils')
 const logger = require('../logger')
-
-/**
- * Provider interface defines the specifications of any provider implementation
- *
- * @interface
- */
-class Provider {
-  /**
-   *
-   * @param {object} options
-   */
-  constructor (options) {
-    return this
-  }
-
-  /**
-   *
-   * @param {object} options
-   * @param {function} cb
-   */
-  list (options, cb) {}
-
-  /**
-   *
-   * @param {object} options
-   * @param {function} cb
-   */
-  download (options, cb) {}
-
-  /**
-   *
-   * @param {object} options
-   * @param {function} cb
-   */
-  thumbnail (options, cb) {}
-
-  /**
-   *
-   * @param {object} options
-   * @param {function} cb
-   */
-  size (options, cb) {}
-
-  /**
-   * @returns {string}
-   */
-  static get authProvider () {
-    return ''
-  }
-}
-
-module.exports.ProviderInterface = Provider
+// eslint-disable-next-line
+const Provider = require('./Provider')
 
 /**
  * adds the desired provider module to the request object,
@@ -90,10 +41,19 @@ module.exports.getProviderMiddleware = (providers) => {
 }
 
 /**
+ * @param {{server: object, providerOptions: object}} companionOptions
  * @return {Object.<string, typeof Provider>}
  */
-module.exports.getDefaultProviders = () => {
-  return { dropbox, drive, instagram, facebook, onedrive }
+module.exports.getDefaultProviders = (companionOptions) => {
+  const { providerOptions } = companionOptions || { providerOptions: null }
+  const providers = { dropbox, drive, facebook, onedrive }
+  if (providerOptions && providerOptions.instagram && providerOptions.instagram.useGraphAPI) {
+    providers.instagram = instagramGraph
+  } else {
+    providers.instagram = instagram
+  }
+
+  return providers
 }
 
 /**
@@ -113,17 +73,17 @@ module.exports.addCustomProviders = (customProviders, providers, grantConfig) =>
 
 /**
  *
- * @param {{server: object, providerOptions: object}} options
+ * @param {{server: object, providerOptions: object}} companionOptions
  * @param {object} grantConfig
  */
-module.exports.addProviderOptions = (options, grantConfig) => {
-  const { server, providerOptions } = options
+module.exports.addProviderOptions = (companionOptions, grantConfig) => {
+  const { server, providerOptions } = companionOptions
   if (!validOptions({ server })) {
     logger.warn('invalid provider options detected. Providers will not be loaded', 'provider.options.invalid')
     return
   }
 
-  grantConfig.server = {
+  grantConfig.defaults = {
     host: server.host,
     protocol: server.protocol,
     path: server.path
@@ -136,13 +96,15 @@ module.exports.addProviderOptions = (options, grantConfig) => {
       // explicitly add providerOptions so users don't override other providerOptions.
       grantConfig[authProvider].key = providerOptions[authProvider].key
       grantConfig[authProvider].secret = providerOptions[authProvider].secret
+      const { provider, name } = authNameToProvider(authProvider, companionOptions)
+      Object.assign(grantConfig[authProvider], provider.getExtraConfig())
 
       // override grant.js redirect uri with companion's custom redirect url
       if (oauthDomain) {
-        const providerName = authToProviderName(authProvider)
+        const providerName = name
         const redirectPath = `/${providerName}/redirect`
         const isExternal = !!server.implicitPath
-        const fullRedirectPath = getURLBuilder(options)(redirectPath, isExternal, true)
+        const fullRedirectPath = getURLBuilder(companionOptions)(redirectPath, isExternal, true)
         grantConfig[authProvider].redirect_uri = `${server.protocol}://${oauthDomain}${fullRedirectPath}`
       }
 
@@ -161,14 +123,16 @@ module.exports.addProviderOptions = (options, grantConfig) => {
 /**
  *
  * @param {string} authProvider
+ * @param {{server: object, providerOptions: object}} options
+ * @return {{name: string, provider: typeof Provider}}
  */
-const authToProviderName = (authProvider) => {
-  const providers = exports.getDefaultProviders()
+const authNameToProvider = (authProvider, options) => {
+  const providers = exports.getDefaultProviders(options)
   const providerNames = Object.keys(providers)
   for (const name of providerNames) {
     const provider = providers[name]
     if (provider.authProvider === authProvider) {
-      return name
+      return { name, provider }
     }
   }
 }

+ 72 - 0
packages/@uppy/companion/src/server/provider/instagram/graph/adapter.js

@@ -0,0 +1,72 @@
+const querystring = require('querystring')
+
+const MEDIA_TYPES = Object.freeze({
+  video: 'VIDEO',
+  carousel: 'CAROUSEL_ALBUM',
+  image: 'IMAGE'
+})
+
+const isVideo = (item) => item.media_type === MEDIA_TYPES.video
+
+exports.isFolder = (_) => {
+  return false
+}
+
+exports.getItemIcon = (item) => {
+  return isVideo(item) ? item.thumbnail_url : item.media_url
+}
+
+exports.getItemSubList = (item) => {
+  const newItems = []
+  item.data.forEach((subItem) => {
+    // exclude videos because of bug https://developers.facebook.com/support/bugs/801145630390846/
+    // @todo remove this clause when bug is fixed
+    if (isVideo(subItem)) {
+      return
+    }
+
+    if (subItem.media_type === MEDIA_TYPES.carousel) {
+      subItem.children.data.forEach((i) => {
+        // exclude videos because of bug https://developers.facebook.com/support/bugs/801145630390846/
+        // @todo remove this clause when bug is fixed
+        if (isVideo(i)) {
+          return
+        }
+
+        newItems.push(i)
+      })
+    } else {
+      newItems.push(subItem)
+    }
+  })
+  return newItems
+}
+
+exports.getItemName = (item, index) => {
+  const ext = isVideo(item) ? 'mp4' : 'jpeg'
+  // adding index, so the name is unique
+  return `Instagram ${item.timestamp}${index}.${ext}`
+}
+
+exports.getMimeType = (item) => {
+  return isVideo(item) ? 'video/mp4' : 'image/jpeg'
+}
+
+exports.getItemId = (item) => item.id
+
+exports.getItemRequestPath = (item) => item.id
+
+exports.getItemModifiedDate = (item) => item.timestamp
+
+exports.getItemThumbnailUrl = (item) => exports.getItemIcon(item)
+
+exports.getNextPagePath = (data, currentQuery, currentPath) => {
+  if (!data.paging || !data.paging.cursors) {
+    return null
+  }
+
+  const query = Object.assign({}, currentQuery, {
+    cursor: data.paging.cursors.after
+  })
+  return `${currentPath || ''}?${querystring.stringify(query)}`
+}

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

@@ -0,0 +1,156 @@
+const Provider = require('../../Provider')
+
+const request = require('request')
+const purest = require('purest')({ request })
+const utils = require('../../../helpers/utils')
+const logger = require('../../../logger')
+const adapter = require('./adapter')
+const AuthError = require('../../error')
+
+class Instagram extends Provider {
+  constructor (options) {
+    super(options)
+    this.authProvider = options.provider = Instagram.authProvider
+    this.client = purest(options)
+  }
+
+  static getExtraConfig () {
+    return {
+      protocol: 'https',
+      credentials_fields: { key: 'app_id', secret: 'app_secret' },
+      scope: ['user_profile', 'user_media'],
+      scope_delimiter: ','
+    }
+  }
+
+  static get authProvider () {
+    return 'instagram'
+  }
+
+  list ({ directory, token, query = {} }, done) {
+    const qs = {
+      fields: 'id,media_type,thumbnail_url,media_url,timestamp,children{media_type,media_url,thumbnail_url,timestamp}'
+    }
+
+    if (query.cursor) {
+      qs.after = query.cursor
+    }
+
+    this.client
+      .get('https://graph.instagram.com/me/media')
+      .qs(qs)
+      .auth(token)
+      .request((err, resp, body) => {
+        if (err || resp.statusCode !== 200) {
+          err = this._error(err, resp)
+          logger.error(err, 'provider.instagram.list.error')
+          return done(err)
+        } else {
+          this._getUsername(token, (err, username) => {
+            err ? done(err) : done(null, this.adaptData(body, username, directory, query))
+          })
+        }
+      })
+  }
+
+  _getUsername (token, done) {
+    this.client
+      .get('https://graph.instagram.com/me')
+      .qs({ fields: 'username' })
+      .auth(token)
+      .request((err, resp, body) => {
+        if (err || resp.statusCode !== 200) {
+          err = this._error(err, resp)
+          logger.error(err, 'provider.instagram.user.error')
+          return done(err)
+        } else {
+          done(null, body.username)
+        }
+      })
+  }
+
+  download ({ id, token }, onData) {
+    return this.client
+      .get(`https://graph.instagram.com/${id}`)
+      .qs({ fields: 'media_url' })
+      .auth(token)
+      .request((err, resp, body) => {
+        if (err) return logger.error(err, 'provider.instagram.download.error')
+        request(body.media_url)
+          .on('data', onData)
+          .on('end', () => onData(null))
+          .on('error', (err) => {
+            logger.error(err, 'provider.instagram.download.url.error')
+          })
+      })
+  }
+
+  thumbnail (_, done) {
+    // not implementing this because a public thumbnail from instagram will be used instead
+    const err = new Error('call to thumbnail is not implemented')
+    logger.error(err, 'provider.instagram.thumbnail.error')
+    return done(err)
+  }
+
+  size ({ id, token }, done) {
+    return this.client
+      .get(`https://graph.instagram.com/${id}`)
+      .qs({ fields: 'media_url' })
+      .auth(token)
+      .request((err, resp, body) => {
+        if (err || resp.statusCode !== 200) {
+          err = this._error(err, resp)
+          logger.error(err, 'provider.instagram.size.error')
+          return done(err)
+        }
+
+        utils.getURLMeta(body.media_url)
+          .then(({ size }) => done(null, size))
+          .catch((err) => {
+            logger.error(err, 'provider.instagram.size.error')
+            done()
+          })
+      })
+  }
+
+  logout (_, done) {
+    // access revoke is not supported by Instagram's API
+    done(null, { revoked: false, manual_revoke_url: 'https://www.instagram.com/accounts/manage_access/' })
+  }
+
+  adaptData (res, username, directory, currentQuery) {
+    const data = { username: username, items: [] }
+    const items = adapter.getItemSubList(res)
+    items.forEach((item, i) => {
+      data.items.push({
+        isFolder: adapter.isFolder(item),
+        icon: adapter.getItemIcon(item),
+        name: adapter.getItemName(item, i),
+        mimeType: adapter.getMimeType(item),
+        id: adapter.getItemId(item),
+        thumbnail: adapter.getItemThumbnailUrl(item),
+        requestPath: adapter.getItemRequestPath(item),
+        modifiedDate: adapter.getItemModifiedDate(item)
+      })
+    })
+
+    data.nextPagePath = adapter.getNextPagePath(res, currentQuery, directory)
+    return data
+  }
+
+  _error (err, resp) {
+    if (resp) {
+      if (resp.body && resp.body.error.code === 190) {
+        // Invalid OAuth 2.0 Access Token
+        return new AuthError()
+      }
+
+      const msg = resp.body && resp.body.error ? resp.body.error.message : ''
+      return new Error(`request to ${this.authProvider} returned status: ${resp.statusCode}, message: ${msg}`)
+    }
+
+    return err
+  }
+}
+
+module.exports = Instagram

+ 4 - 1
packages/@uppy/companion/src/server/provider/instagram/index.js

@@ -1,3 +1,5 @@
+const Provider = require('../Provider')
+
 const request = require('request')
 const purest = require('purest')({ request })
 const utils = require('../../helpers/utils')
@@ -5,8 +7,9 @@ const logger = require('../../logger')
 const adapter = require('./adapter')
 const AuthError = require('../error')
 
-class Instagram {
+class Instagram extends Provider {
   constructor (options) {
+    super(options)
     this.authProvider = options.provider = Instagram.authProvider
     this.client = purest(options)
   }

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

@@ -1,11 +1,14 @@
+const Provider = require('../Provider')
+
 const request = require('request')
 const purest = require('purest')({ request })
 const logger = require('../../logger')
 const adapter = require('./adapter')
 const AuthError = require('../error')
 
-class OneDrive {
+class OneDrive extends Provider {
   constructor (options) {
+    super(options)
     this.authProvider = options.provider = OneDrive.authProvider
     this.client = purest(options)
   }

+ 2 - 1
packages/@uppy/companion/src/standalone/helper.js

@@ -39,7 +39,8 @@ const getConfigFromEnv = () => {
       },
       instagram: {
         key: process.env.COMPANION_INSTAGRAM_KEY,
-        secret: getSecret('COMPANION_INSTAGRAM_SECRET')
+        secret: getSecret('COMPANION_INSTAGRAM_SECRET'),
+        useGraphAPI: !!process.env.COMPANION_INSTAGRAM_USE_GRAPH_API
       },
       facebook: {
         key: process.env.COMPANION_FACEBOOK_KEY,

+ 39 - 2
packages/@uppy/companion/test/__tests__/provider-manager.js

@@ -1,13 +1,14 @@
 /* global jest:false, test:false, expect:false, describe:false, beforeEach:false */
 
 const providerManager = require('../../src/server/provider')
+const { getCompanionOptions } = require('../../src/standalone/helper')
 let grantConfig
 let companionOptions
 
 describe('Test Provider options', () => {
   beforeEach(() => {
     grantConfig = require('../../src/config/grant')()
-    companionOptions = require('../../src/standalone/helper').getCompanionOptions()
+    companionOptions = getCompanionOptions()
   })
 
   test('adds provider options', () => {
@@ -22,12 +23,48 @@ describe('Test Provider options', () => {
     expect(grantConfig.instagram.secret).toBe('instagram_secret')
   })
 
+  test('adds extra provider config', () => {
+    process.env.COMPANION_INSTAGRAM_USE_GRAPH_API = 'truthy value'
+    providerManager.addProviderOptions(getCompanionOptions(), grantConfig)
+    expect(grantConfig.instagram).toEqual({
+      transport: 'session',
+      callback: '/instagram/callback',
+      key: 'instagram_key',
+      secret: 'instagram_secret',
+      protocol: 'https',
+      credentials_fields: { key: 'app_id', secret: 'app_secret' },
+      scope: ['user_profile', 'user_media'],
+      scope_delimiter: ','
+    })
+
+    expect(grantConfig.dropbox).toEqual({
+      key: 'dropbox_key',
+      secret: 'dropbox_secret',
+      transport: 'session',
+      authorize_url: 'https://www.dropbox.com/oauth2/authorize',
+      access_url: 'https://api.dropbox.com/oauth2/token',
+      callback: '/dropbox/callback'
+    })
+
+    expect(grantConfig.google).toEqual({
+      key: 'google_key',
+      secret: 'google_secret',
+      transport: 'session',
+      scope: [
+        'https://www.googleapis.com/auth/drive.readonly'
+      ],
+      callback: '/drive/callback'
+    })
+
+    process.env.COMPANION_INSTAGRAM_USE_GRAPH_API = ''
+  })
+
   test('adds provider options for secret files', () => {
     process.env.COMPANION_DROPBOX_SECRET_FILE = process.env.PWD + '/test/resources/dropbox_secret_file'
     process.env.COMPANION_GOOGLE_SECRET_FILE = process.env.PWD + '/test/resources/google_secret_file'
     process.env.COMPANION_INSTAGRAM_SECRET_FILE = process.env.PWD + '/test/resources/instagram_secret_file'
 
-    companionOptions = require('../../src/standalone/helper').getCompanionOptions()
+    companionOptions = getCompanionOptions()
 
     providerManager.addProviderOptions(companionOptions, grantConfig)
 

+ 1 - 1
packages/@uppy/companion/test/mockserver.js

@@ -12,7 +12,7 @@ authServer.all('*/callback', (req, res, next) => {
   next()
 })
 authServer.all('/drive/send-token', (req, res, next) => {
-  req.session.grant = { state: req.query.state || 'non-empty-value' }
+  req.session.grant = { dynamic: { state: req.query.state || 'non-empty-value' } }
   next()
 })