mirror of
https://github.com/transloadit/uppy.git
synced 2026-01-23 02:25:07 +00:00
Refactor Companion to ESM (#5803)
- convert cjs to esm - refactor from jest to vitest closes #3979 --------- Co-authored-by: Merlijn Vos <merlijn@soverin.net>
This commit is contained in:
parent
017e8ae608
commit
acdc683d47
105 changed files with 1242 additions and 2144 deletions
5
.changeset/mean-eels-scream.md
Normal file
5
.changeset/mean-eels-scream.md
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
"@uppy/companion": major
|
||||
---
|
||||
|
||||
Make Companion ESM-only. As of Node.js 20.19.0, you can require(esm) if you haven't transitioned yet.
|
||||
|
|
@ -31,7 +31,7 @@ function adaptData(res) {
|
|||
/**
|
||||
* an example of a custom provider module. It implements @uppy/companion's Provider interface
|
||||
*/
|
||||
class MyCustomProvider {
|
||||
export default class MyCustomProvider {
|
||||
static version = 2
|
||||
|
||||
static get oauthProvider() {
|
||||
|
|
@ -76,5 +76,3 @@ class MyCustomProvider {
|
|||
return { stream: Readable.fromWeb(resp.body), size }
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = MyCustomProvider
|
||||
22
packages/@uppy/companion/__mocks__/express-prom-bundle.js
Normal file
22
packages/@uppy/companion/__mocks__/express-prom-bundle.js
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
class Gauge {
|
||||
set = () => {}
|
||||
}
|
||||
|
||||
export default function () {
|
||||
const middleware = (req, res, next) => {
|
||||
// simulate prometheus metrics endpoint:
|
||||
if (req.url === '/metrics') {
|
||||
res.setHeader('Content-Type', 'text/plain')
|
||||
res.end('# Dummy metrics\n')
|
||||
return
|
||||
}
|
||||
next()
|
||||
}
|
||||
|
||||
middleware.promClient = {
|
||||
collectDefaultMetrics: () => {},
|
||||
Gauge,
|
||||
}
|
||||
|
||||
return middleware
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
class Upload {
|
||||
export class Upload {
|
||||
constructor(file, options) {
|
||||
this.url = 'https://tus.endpoint/files/foo-bar'
|
||||
this.options = options
|
||||
|
|
@ -18,4 +18,4 @@ class Upload {
|
|||
}
|
||||
}
|
||||
|
||||
module.exports = { Upload }
|
||||
export default { Upload }
|
||||
|
|
@ -1,3 +1,3 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
require('../lib/standalone/start-server')
|
||||
import '../lib/standalone/start-server.js'
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
"author": "Transloadit.com",
|
||||
"license": "MIT",
|
||||
"homepage": "https://github.com/transloadit/uppy#readme",
|
||||
"type": "module",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/transloadit/uppy.git"
|
||||
|
|
@ -43,12 +44,12 @@
|
|||
"escape-string-regexp": "4.0.0",
|
||||
"express": "4.21.2",
|
||||
"express-interceptor": "1.2.0",
|
||||
"express-prom-bundle": "7.0.0",
|
||||
"express-prom-bundle": "7",
|
||||
"express-session": "1.18.1",
|
||||
"fast-safe-stringify": "^2.1.1",
|
||||
"formdata-node": "^6.0.3",
|
||||
"got": "^13.0.0",
|
||||
"grant": "5.4.22",
|
||||
"grant": "^5.4.24",
|
||||
"helmet": "^7.1.0",
|
||||
"ioredis": "^5.3.2",
|
||||
"ipaddr.js": "^2.0.1",
|
||||
|
|
@ -67,7 +68,7 @@
|
|||
"supports-color": "8.x",
|
||||
"tus-js-client": "^4.1.0",
|
||||
"validator": "^13.0.0",
|
||||
"webdav": "5.7.1",
|
||||
"webdav": "^5.8.0",
|
||||
"ws": "8.17.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
|
@ -88,7 +89,6 @@
|
|||
"@types/ws": "8.5.3",
|
||||
"execa": "^9.6.0",
|
||||
"http-proxy": "^1.18.1",
|
||||
"jest": "^29.0.0",
|
||||
"nock": "^13.1.3",
|
||||
"supertest": "6.2.4",
|
||||
"typescript": "^5.8.3",
|
||||
|
|
@ -98,21 +98,16 @@
|
|||
"bin/",
|
||||
"lib/"
|
||||
],
|
||||
"jest": {
|
||||
"testEnvironment": "node",
|
||||
"testTimeout": 10000,
|
||||
"automock": false
|
||||
},
|
||||
"scripts": {
|
||||
"build": "tsc -p .",
|
||||
"build": "tsc --build tsconfig.build.json",
|
||||
"deploy": "kubectl apply -f infra/kube/companion-kube.yml",
|
||||
"start": "node ./lib/standalone/start-server.js",
|
||||
"start:dev": "bash start-dev",
|
||||
"test": "NODE_OPTIONS=--experimental-vm-modules jest --runInBand --silent",
|
||||
"typecheck": "tsc --build"
|
||||
"typecheck": "tsc --build",
|
||||
"test": "vitest run --silent='passed-only'"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.20.0 || ^20.15.0 || >=22.0.0"
|
||||
"node": "^20.19.3 || >=22.0.0"
|
||||
},
|
||||
"installConfig": {
|
||||
"hoistingLimits": "workspaces"
|
||||
|
|
|
|||
|
|
@ -1,40 +1,42 @@
|
|||
const express = require('express')
|
||||
const Grant = require('grant').default.express()
|
||||
const merge = require('lodash/merge')
|
||||
const cookieParser = require('cookie-parser')
|
||||
const interceptor = require('express-interceptor')
|
||||
const { randomUUID } = require('node:crypto')
|
||||
|
||||
const grantConfig = require('./config/grant')()
|
||||
const providerManager = require('./server/provider')
|
||||
const controllers = require('./server/controllers')
|
||||
const s3 = require('./server/controllers/s3')
|
||||
const url = require('./server/controllers/url')
|
||||
const googlePicker = require('./server/controllers/googlePicker')
|
||||
const createEmitter = require('./server/emitter')
|
||||
const redis = require('./server/redis')
|
||||
const jobs = require('./server/jobs')
|
||||
const logger = require('./server/logger')
|
||||
const middlewares = require('./server/middlewares')
|
||||
const {
|
||||
getMaskableSecrets,
|
||||
import { randomUUID } from 'node:crypto'
|
||||
import cookieParser from 'cookie-parser'
|
||||
import express from 'express'
|
||||
import interceptor from 'express-interceptor'
|
||||
import grant from 'grant'
|
||||
import merge from 'lodash/merge.js'
|
||||
import packageJson from '../package.json' with { type: 'json' }
|
||||
import {
|
||||
defaultOptions,
|
||||
getMaskableSecrets,
|
||||
validateConfig,
|
||||
} = require('./config/companion')
|
||||
const {
|
||||
} from './config/companion.js'
|
||||
import grantConfigFn from './config/grant.js'
|
||||
import googlePicker from './server/controllers/googlePicker.js'
|
||||
import * as controllers from './server/controllers/index.js'
|
||||
import s3 from './server/controllers/s3.js'
|
||||
import url from './server/controllers/url.js'
|
||||
import createEmitter from './server/emitter/index.js'
|
||||
import { getURLBuilder } from './server/helpers/utils.js'
|
||||
import * as jobs from './server/jobs.js'
|
||||
import logger from './server/logger.js'
|
||||
import * as middlewares from './server/middlewares.js'
|
||||
import { getCredentialsOverrideMiddleware } from './server/provider/credentials.js'
|
||||
import {
|
||||
ProviderApiError,
|
||||
ProviderUserError,
|
||||
ProviderAuthError,
|
||||
} = require('./server/provider/error')
|
||||
const {
|
||||
getCredentialsOverrideMiddleware,
|
||||
} = require('./server/provider/credentials')
|
||||
const { getURLBuilder } = require('./server/helpers/utils')
|
||||
// @ts-ignore
|
||||
const { version } = require('../package.json')
|
||||
const { isOAuthProvider } = require('./server/provider/Provider')
|
||||
ProviderUserError,
|
||||
} from './server/provider/error.js'
|
||||
import * as providerManager from './server/provider/index.js'
|
||||
import { isOAuthProvider } from './server/provider/Provider.js'
|
||||
import * as redis from './server/redis.js'
|
||||
|
||||
function setLoggerProcessName({ loggerProcessName }) {
|
||||
import socket from './server/socket.js'
|
||||
|
||||
export { socket }
|
||||
|
||||
const grantConfig = grantConfigFn()
|
||||
|
||||
export function setLoggerProcessName({ loggerProcessName }) {
|
||||
if (loggerProcessName != null) logger.setProcessName(loggerProcessName)
|
||||
}
|
||||
|
||||
|
|
@ -72,14 +74,11 @@ const interceptGrantErrorResponse = interceptor((req, res) => {
|
|||
})
|
||||
|
||||
// make the errors available publicly for custom providers
|
||||
module.exports.errors = {
|
||||
export const errors = {
|
||||
ProviderApiError,
|
||||
ProviderUserError,
|
||||
ProviderAuthError,
|
||||
}
|
||||
module.exports.socket = require('./server/socket')
|
||||
|
||||
module.exports.setLoggerProcessName = setLoggerProcessName
|
||||
|
||||
/**
|
||||
* Entry point into initializing the Companion app.
|
||||
|
|
@ -87,7 +86,7 @@ module.exports.setLoggerProcessName = setLoggerProcessName
|
|||
* @param {object} optionsArg
|
||||
* @returns {{ app: import('express').Express, emitter: any }}}
|
||||
*/
|
||||
module.exports.app = (optionsArg = {}) => {
|
||||
export function app(optionsArg = {}) {
|
||||
setLoggerProcessName(optionsArg)
|
||||
|
||||
validateConfig(optionsArg)
|
||||
|
|
@ -131,7 +130,7 @@ module.exports.app = (optionsArg = {}) => {
|
|||
express.urlencoded({ extended: false }),
|
||||
getCredentialsOverrideMiddleware(providers, options),
|
||||
)
|
||||
app.use(Grant(grantConfig))
|
||||
app.use(grant.default.express(grantConfig))
|
||||
|
||||
app.use((req, res, next) => {
|
||||
if (options.sendSelfEndpoint) {
|
||||
|
|
@ -307,7 +306,7 @@ module.exports.app = (optionsArg = {}) => {
|
|||
interval: options.periodicPingInterval,
|
||||
count: options.periodicPingCount,
|
||||
staticPayload: options.periodicPingStaticPayload,
|
||||
version,
|
||||
version: packageJson.version,
|
||||
processId,
|
||||
})
|
||||
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
const fs = require('node:fs')
|
||||
const { isURL } = require('validator')
|
||||
const logger = require('../server/logger')
|
||||
const { defaultGetKey } = require('../server/helpers/utils')
|
||||
import fs from 'node:fs'
|
||||
import validator from 'validator'
|
||||
import { defaultGetKey } from '../server/helpers/utils.js'
|
||||
import logger from '../server/logger.js'
|
||||
|
||||
const defaultOptions = {
|
||||
export const defaultOptions = {
|
||||
server: {
|
||||
protocol: 'http',
|
||||
path: '',
|
||||
|
|
@ -28,7 +28,7 @@ const defaultOptions = {
|
|||
/**
|
||||
* @param {object} companionOptions
|
||||
*/
|
||||
function getMaskableSecrets(companionOptions) {
|
||||
export function getMaskableSecrets(companionOptions) {
|
||||
const secrets = []
|
||||
const { providerOptions, customProviders, s3 } = companionOptions
|
||||
|
||||
|
|
@ -60,7 +60,7 @@ function getMaskableSecrets(companionOptions) {
|
|||
*
|
||||
* @param {object} companionOptions
|
||||
*/
|
||||
const validateConfig = (companionOptions) => {
|
||||
export const validateConfig = (companionOptions) => {
|
||||
const mandatoryOptions = ['secret', 'filePath', 'server.host']
|
||||
/** @type {string[]} */
|
||||
const unspecified = []
|
||||
|
|
@ -148,7 +148,7 @@ const validateConfig = (companionOptions) => {
|
|||
(!Array.isArray(periodicPingUrls) ||
|
||||
periodicPingUrls.some(
|
||||
(url2) =>
|
||||
!isURL(url2, {
|
||||
!validator.isURL(url2, {
|
||||
protocols: ['http', 'https'],
|
||||
require_protocol: true,
|
||||
require_tld: false,
|
||||
|
|
@ -162,9 +162,3 @@ const validateConfig = (companionOptions) => {
|
|||
throw new TypeError('Option maxFilenameLength must be greater than 0')
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
defaultOptions,
|
||||
getMaskableSecrets,
|
||||
validateConfig,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ const defaults = {
|
|||
}
|
||||
|
||||
// oauth configuration for provider services that are used.
|
||||
module.exports = () => {
|
||||
export default () => {
|
||||
return {
|
||||
// we need separate auth providers because scopes are different,
|
||||
// and because it would be a too big rewrite to allow reuse of the same provider.
|
||||
|
|
|
|||
|
|
@ -1,31 +1,27 @@
|
|||
const tus = require('tus-js-client')
|
||||
const { randomUUID } = require('node:crypto')
|
||||
const validator = require('validator')
|
||||
const { pipeline } = require('node:stream/promises')
|
||||
const { join } = require('node:path')
|
||||
const fs = require('node:fs')
|
||||
const throttle = require('lodash/throttle')
|
||||
const { once } = require('node:events')
|
||||
const { FormData } = require('formdata-node')
|
||||
|
||||
const { Upload } = require('@aws-sdk/lib-storage')
|
||||
|
||||
const {
|
||||
rfc2047EncodeMetadata,
|
||||
import { randomUUID } from 'node:crypto'
|
||||
import { once } from 'node:events'
|
||||
import fs, { createReadStream, createWriteStream, ReadStream } from 'node:fs'
|
||||
import { stat, unlink } from 'node:fs/promises'
|
||||
import { join } from 'node:path'
|
||||
import { pipeline } from 'node:stream/promises'
|
||||
import { Upload } from '@aws-sdk/lib-storage'
|
||||
import { FormData } from 'formdata-node'
|
||||
import got from 'got'
|
||||
import throttle from 'lodash/throttle.js'
|
||||
import { serializeError } from 'serialize-error'
|
||||
import tus from 'tus-js-client'
|
||||
import validator from 'validator'
|
||||
import emitter from './emitter/index.js'
|
||||
import headerSanitize from './header-blacklist.js'
|
||||
import {
|
||||
getBucket,
|
||||
hasMatch,
|
||||
jsonStringify,
|
||||
rfc2047EncodeMetadata,
|
||||
truncateFilename,
|
||||
} = require('./helpers/utils')
|
||||
|
||||
const got = require('./got')
|
||||
|
||||
const { createReadStream, createWriteStream, ReadStream } = fs
|
||||
const { stat, unlink } = fs.promises
|
||||
|
||||
const emitter = require('./emitter')
|
||||
const { jsonStringify, hasMatch } = require('./helpers/utils')
|
||||
const logger = require('./logger')
|
||||
const headerSanitize = require('./header-blacklist')
|
||||
const redis = require('./redis')
|
||||
} from './helpers/utils.js'
|
||||
import * as logger from './logger.js'
|
||||
import * as redis from './redis.js'
|
||||
|
||||
// Need to limit length or we can get
|
||||
// "MetadataTooLarge: Your metadata headers exceed the maximum allowed metadata size" in tus / S3
|
||||
|
|
@ -40,7 +36,7 @@ function exceedsMaxFileSize(maxFileSize, size) {
|
|||
return maxFileSize && size && size > maxFileSize
|
||||
}
|
||||
|
||||
class ValidationError extends Error {
|
||||
export class ValidationError extends Error {
|
||||
name = 'ValidationError'
|
||||
}
|
||||
|
||||
|
|
@ -126,7 +122,7 @@ const states = {
|
|||
done: 'done',
|
||||
}
|
||||
|
||||
class Uploader {
|
||||
export default class Uploader {
|
||||
/** @type {import('ioredis').Redis} */
|
||||
storage
|
||||
|
||||
|
|
@ -527,8 +523,6 @@ class Uploader {
|
|||
async #emitError(err) {
|
||||
// delete stack to avoid sending server info to client
|
||||
// see PR discussion https://github.com/transloadit/uppy/pull/3832
|
||||
// @ts-ignore
|
||||
const { serializeError } = await import('serialize-error')
|
||||
const { stack, ...serializedErr } = serializeError(err)
|
||||
const dataToEmit = {
|
||||
action: 'error',
|
||||
|
|
@ -688,7 +682,7 @@ class Uploader {
|
|||
try {
|
||||
const httpMethod =
|
||||
(this.options.httpMethod || '').toUpperCase() === 'PUT' ? 'put' : 'post'
|
||||
const runRequest = (await got)[httpMethod]
|
||||
const runRequest = await got[httpMethod]
|
||||
|
||||
const response = await runRequest(url, reqOptions)
|
||||
|
||||
|
|
@ -779,6 +773,3 @@ class Uploader {
|
|||
|
||||
Uploader.FILE_NAME_PREFIX = 'uppy-file'
|
||||
Uploader.STORAGE_PREFIX = 'companion'
|
||||
|
||||
module.exports = Uploader
|
||||
module.exports.ValidationError = ValidationError
|
||||
|
|
|
|||
|
|
@ -1,11 +1,10 @@
|
|||
/**
|
||||
* oAuth callback. Encrypts the access token and sends the new token with the response,
|
||||
*/
|
||||
const serialize = require('serialize-javascript')
|
||||
|
||||
const tokenService = require('../helpers/jwt')
|
||||
const logger = require('../logger')
|
||||
const oAuthState = require('../helpers/oauth-state')
|
||||
import serialize from 'serialize-javascript'
|
||||
import * as tokenService from '../helpers/jwt.js'
|
||||
import * as oAuthState from '../helpers/oauth-state.js'
|
||||
import logger from '../logger.js'
|
||||
|
||||
const closePageHtml = (origin) => `
|
||||
<!DOCTYPE html>
|
||||
|
|
@ -28,7 +27,7 @@ const closePageHtml = (origin) => `
|
|||
* @param {object} res
|
||||
* @param {Function} next
|
||||
*/
|
||||
module.exports = function callback(req, res, next) {
|
||||
export default function callback(req, res, next) {
|
||||
const { providerName } = req.params
|
||||
|
||||
const grant = req.session.grant || {}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
const oAuthState = require('../helpers/oauth-state')
|
||||
import * as oAuthState from '../helpers/oauth-state.js'
|
||||
|
||||
/**
|
||||
* Derived from `cors` npm package.
|
||||
|
|
@ -82,7 +82,7 @@ function getClientOrigin(base64EncodedState) {
|
|||
* @param {object} req
|
||||
* @param {object} res
|
||||
*/
|
||||
module.exports = function connect(req, res, next) {
|
||||
export default function connect(req, res, next) {
|
||||
const stateObj = oAuthState.generateState()
|
||||
|
||||
if (req.companion.options.server.oauthDomain) {
|
||||
|
|
@ -116,4 +116,5 @@ module.exports = function connect(req, res, next) {
|
|||
}
|
||||
encodeStateAndRedirect(req, res, stateObj)
|
||||
}
|
||||
module.exports.isOriginAllowed = isOriginAllowed
|
||||
|
||||
export { isOriginAllowed }
|
||||
|
|
|
|||
|
|
@ -1,6 +1,10 @@
|
|||
const { respondWithError } = require('../provider/error')
|
||||
import { respondWithError } from '../provider/error.js'
|
||||
|
||||
async function deauthCallback({ body, companion, headers }, res, next) {
|
||||
export default async function deauthCallback(
|
||||
{ body, companion, headers },
|
||||
res,
|
||||
next,
|
||||
) {
|
||||
// we need the provider instance to decide status codes because
|
||||
// this endpoint does not cater to a uniform client.
|
||||
// It doesn't respond to Uppy client like other endpoints.
|
||||
|
|
@ -17,5 +21,3 @@ async function deauthCallback({ body, companion, headers }, res, next) {
|
|||
next(err)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = deauthCallback
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
const logger = require('../logger')
|
||||
const { startDownUpload } = require('../helpers/upload')
|
||||
const { respondWithError } = require('../provider/error')
|
||||
import { startDownUpload } from '../helpers/upload.js'
|
||||
import logger from '../logger.js'
|
||||
import { respondWithError } from '../provider/error.js'
|
||||
|
||||
async function get(req, res) {
|
||||
export default async function get(req, res) {
|
||||
const { id } = req.params
|
||||
const { providerUserSession } = req.companion
|
||||
const { provider } = req.companion
|
||||
|
|
@ -22,5 +22,3 @@ async function get(req, res) {
|
|||
res.status(500).json({ message: 'Failed to download file' })
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = get
|
||||
|
|
|
|||
|
|
@ -1,12 +1,11 @@
|
|||
const express = require('express')
|
||||
const assert = require('node:assert')
|
||||
|
||||
const { startDownUpload } = require('../helpers/upload')
|
||||
const { validateURL } = require('../helpers/request')
|
||||
const logger = require('../logger')
|
||||
const { downloadURL } = require('../download')
|
||||
const { streamGoogleFile } = require('../provider/google/drive')
|
||||
const { respondWithError } = require('../provider/error')
|
||||
import assert from 'node:assert'
|
||||
import express from 'express'
|
||||
import { downloadURL } from '../download.js'
|
||||
import { validateURL } from '../helpers/request.js'
|
||||
import { startDownUpload } from '../helpers/upload.js'
|
||||
import logger from '../logger.js'
|
||||
import { respondWithError } from '../provider/error.js'
|
||||
import { streamGoogleFile } from '../provider/google/drive/index.js'
|
||||
|
||||
const getAuthHeader = (token) => ({ authorization: `Bearer ${token}` })
|
||||
|
||||
|
|
@ -47,4 +46,4 @@ const get = async (req, res) => {
|
|||
}
|
||||
}
|
||||
|
||||
module.exports = () => express.Router().post('/get', express.json(), get)
|
||||
export default () => express.Router().post('/get', express.json(), get)
|
||||
|
|
|
|||
|
|
@ -1,14 +1,12 @@
|
|||
module.exports = {
|
||||
callback: require('./callback'),
|
||||
deauthorizationCallback: require('./deauth-callback'),
|
||||
sendToken: require('./send-token'),
|
||||
get: require('./get'),
|
||||
thumbnail: require('./thumbnail'),
|
||||
list: require('./list'),
|
||||
simpleAuth: require('./simple-auth'),
|
||||
logout: require('./logout'),
|
||||
connect: require('./connect'),
|
||||
preauth: require('./preauth'),
|
||||
redirect: require('./oauth-redirect'),
|
||||
refreshToken: require('./refresh-token'),
|
||||
}
|
||||
export { default as callback } from './callback.js'
|
||||
export { default as connect } from './connect.js'
|
||||
export { default as deauthorizationCallback } from './deauth-callback.js'
|
||||
export { default as get } from './get.js'
|
||||
export { default as list } from './list.js'
|
||||
export { default as logout } from './logout.js'
|
||||
export { default as redirect } from './oauth-redirect.js'
|
||||
export { default as preauth } from './preauth.js'
|
||||
export { default as refreshToken } from './refresh-token.js'
|
||||
export { default as sendToken } from './send-token.js'
|
||||
export { default as simpleAuth } from './simple-auth.js'
|
||||
export { default as thumbnail } from './thumbnail.js'
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
const { respondWithError } = require('../provider/error')
|
||||
import { respondWithError } from '../provider/error.js'
|
||||
|
||||
async function list({ query, params, companion }, res, next) {
|
||||
export default async function list({ query, params, companion }, res, next) {
|
||||
const { providerUserSession } = companion
|
||||
|
||||
try {
|
||||
|
|
@ -16,5 +16,3 @@ async function list({ query, params, companion }, res, next) {
|
|||
next(err)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = list
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
const tokenService = require('../helpers/jwt')
|
||||
const { respondWithError } = require('../provider/error')
|
||||
import * as tokenService from '../helpers/jwt.js'
|
||||
import { respondWithError } from '../provider/error.js'
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {object} req
|
||||
* @param {object} res
|
||||
*/
|
||||
async function logout(req, res, next) {
|
||||
export default async function logout(req, res, next) {
|
||||
const cleanSession = () => {
|
||||
if (req.session.grant) {
|
||||
req.session.grant.state = null
|
||||
|
|
@ -40,5 +40,3 @@ async function logout(req, res, next) {
|
|||
next(err)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = logout
|
||||
|
|
|
|||
|
|
@ -1,14 +1,14 @@
|
|||
const qs = require('node:querystring')
|
||||
const { URL } = require('node:url')
|
||||
const { hasMatch } = require('../helpers/utils')
|
||||
const oAuthState = require('../helpers/oauth-state')
|
||||
import qs from 'node:querystring'
|
||||
import { URL } from 'node:url'
|
||||
import * as oAuthState from '../helpers/oauth-state.js'
|
||||
import { hasMatch } from '../helpers/utils.js'
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {object} req
|
||||
* @param {object} res
|
||||
*/
|
||||
module.exports = function oauthRedirect(req, res) {
|
||||
export default function oauthRedirect(req, res) {
|
||||
const params = qs.stringify(req.query)
|
||||
const { oauthProvider } = req.companion.providerClass
|
||||
if (!req.companion.options.server.oauthDomain) {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
const tokenService = require('../helpers/jwt')
|
||||
const logger = require('../logger')
|
||||
import * as tokenService from '../helpers/jwt.js'
|
||||
import logger from '../logger.js'
|
||||
|
||||
function preauth(req, res) {
|
||||
export default function preauth(req, res) {
|
||||
if (!req.body || !req.body.params) {
|
||||
logger.info('invalid request data received', 'preauth.bad')
|
||||
return res.sendStatus(400)
|
||||
|
|
@ -19,5 +19,3 @@ function preauth(req, res) {
|
|||
)
|
||||
return res.json({ token: preAuthToken })
|
||||
}
|
||||
|
||||
module.exports = preauth
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
const tokenService = require('../helpers/jwt')
|
||||
const { respondWithError } = require('../provider/error')
|
||||
const logger = require('../logger')
|
||||
import * as tokenService from '../helpers/jwt.js'
|
||||
import logger from '../logger.js'
|
||||
import { respondWithError } from '../provider/error.js'
|
||||
|
||||
// https://www.dropboxforum.com/t5/Dropbox-API-Support-Feedback/Get-refresh-token-from-access-token/td-p/596739
|
||||
// https://developers.dropbox.com/oauth-guide
|
||||
// https://github.com/simov/grant/issues/149
|
||||
async function refreshToken(req, res, next) {
|
||||
export default async function refreshToken(req, res, next) {
|
||||
const { providerName } = req.params
|
||||
|
||||
const { key: clientId, secret: clientSecret } = req.companion.options
|
||||
|
|
@ -61,5 +61,3 @@ async function refreshToken(req, res, next) {
|
|||
next(err)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = refreshToken
|
||||
|
|
|
|||
|
|
@ -1,23 +1,21 @@
|
|||
const express = require('express')
|
||||
const {
|
||||
import {
|
||||
AbortMultipartUploadCommand,
|
||||
CompleteMultipartUploadCommand,
|
||||
CreateMultipartUploadCommand,
|
||||
ListPartsCommand,
|
||||
UploadPartCommand,
|
||||
AbortMultipartUploadCommand,
|
||||
CompleteMultipartUploadCommand,
|
||||
} = require('@aws-sdk/client-s3')
|
||||
const { STSClient, GetFederationTokenCommand } = require('@aws-sdk/client-sts')
|
||||
|
||||
const { createPresignedPost } = require('@aws-sdk/s3-presigned-post')
|
||||
const { getSignedUrl } = require('@aws-sdk/s3-request-presigner')
|
||||
|
||||
const {
|
||||
rfc2047EncodeMetadata,
|
||||
} from '@aws-sdk/client-s3'
|
||||
import { GetFederationTokenCommand, STSClient } from '@aws-sdk/client-sts'
|
||||
import { createPresignedPost } from '@aws-sdk/s3-presigned-post'
|
||||
import { getSignedUrl } from '@aws-sdk/s3-request-presigner'
|
||||
import express from 'express'
|
||||
import {
|
||||
getBucket,
|
||||
rfc2047EncodeMetadata,
|
||||
truncateFilename,
|
||||
} = require('../helpers/utils')
|
||||
} from '../helpers/utils.js'
|
||||
|
||||
module.exports = function s3(config) {
|
||||
export default function s3(config) {
|
||||
if (typeof config.acl !== 'string' && config.acl != null) {
|
||||
throw new TypeError('s3: The `acl` option must be a string or null')
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
const serialize = require('serialize-javascript')
|
||||
const { isOriginAllowed } = require('./connect')
|
||||
|
||||
const oAuthState = require('../helpers/oauth-state')
|
||||
import serialize from 'serialize-javascript'
|
||||
import * as oAuthState from '../helpers/oauth-state.js'
|
||||
import { isOriginAllowed } from './connect.js'
|
||||
|
||||
/**
|
||||
*
|
||||
|
|
@ -51,7 +50,7 @@ const htmlContent = (token, origin) => {
|
|||
* @param {import('express').Response} res
|
||||
* @param {import('express').NextFunction} next
|
||||
*/
|
||||
module.exports = function sendToken(req, res, next) {
|
||||
export default function sendToken(req, res, next) {
|
||||
// @ts-expect-error untyped
|
||||
const { companion } = req
|
||||
const uppyAuthToken = companion.authToken
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
const tokenService = require('../helpers/jwt')
|
||||
const { respondWithError } = require('../provider/error')
|
||||
const logger = require('../logger')
|
||||
import * as tokenService from '../helpers/jwt.js'
|
||||
import logger from '../logger.js'
|
||||
import { respondWithError } from '../provider/error.js'
|
||||
|
||||
async function simpleAuth(req, res, next) {
|
||||
export default async function simpleAuth(req, res, next) {
|
||||
const { providerName } = req.params
|
||||
|
||||
try {
|
||||
|
|
@ -39,5 +39,3 @@ async function simpleAuth(req, res, next) {
|
|||
next(err)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = simpleAuth
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
const { respondWithError } = require('../provider/error')
|
||||
import { respondWithError } from '../provider/error.js'
|
||||
|
||||
/**
|
||||
*
|
||||
|
|
@ -22,4 +22,4 @@ async function thumbnail(req, res, next) {
|
|||
}
|
||||
}
|
||||
|
||||
module.exports = thumbnail
|
||||
export default thumbnail
|
||||
|
|
|
|||
|
|
@ -1,11 +1,9 @@
|
|||
const express = require('express')
|
||||
|
||||
const { startDownUpload } = require('../helpers/upload')
|
||||
const { downloadURL } = require('../download')
|
||||
const { validateURL } = require('../helpers/request')
|
||||
const { getURLMeta } = require('../helpers/request')
|
||||
const logger = require('../logger')
|
||||
const { respondWithError } = require('../provider/error')
|
||||
import express from 'express'
|
||||
import { downloadURL } from '../download.js'
|
||||
import { getURLMeta, validateURL } from '../helpers/request.js'
|
||||
import { startDownUpload } from '../helpers/upload.js'
|
||||
import logger from '../logger.js'
|
||||
import { respondWithError } from '../provider/error.js'
|
||||
|
||||
/**
|
||||
* @callback downloadCallback
|
||||
|
|
@ -73,7 +71,7 @@ const get = async (req, res) => {
|
|||
}
|
||||
}
|
||||
|
||||
module.exports = () =>
|
||||
export default () =>
|
||||
express
|
||||
.Router()
|
||||
.post('/meta', express.json(), meta)
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
const logger = require('./logger')
|
||||
const { getProtectedGot } = require('./helpers/request')
|
||||
const { prepareStream } = require('./helpers/utils')
|
||||
import { getProtectedGot } from './helpers/request.js'
|
||||
import { prepareStream } from './helpers/utils.js'
|
||||
import logger from './logger.js'
|
||||
|
||||
/**
|
||||
* Downloads the content in the specified url, and passes the data
|
||||
|
|
@ -11,9 +11,9 @@ const { prepareStream } = require('./helpers/utils')
|
|||
* @param {string} traceId
|
||||
* @returns {Promise}
|
||||
*/
|
||||
const downloadURL = async (url, allowLocalIPs, traceId, options) => {
|
||||
export const downloadURL = async (url, allowLocalIPs, traceId, options) => {
|
||||
try {
|
||||
const protectedGot = await getProtectedGot({ allowLocalIPs })
|
||||
const protectedGot = getProtectedGot({ allowLocalIPs })
|
||||
const stream = protectedGot.stream.get(url, {
|
||||
responseType: 'json',
|
||||
...options,
|
||||
|
|
@ -25,7 +25,3 @@ const downloadURL = async (url, allowLocalIPs, traceId, options) => {
|
|||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
downloadURL,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
const { EventEmitter } = require('node:events')
|
||||
import { EventEmitter } from 'node:events'
|
||||
|
||||
module.exports = () => {
|
||||
export default function defaultEmitter() {
|
||||
return new EventEmitter()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
const nodeEmitter = require('./default-emitter')
|
||||
const redisEmitter = require('./redis-emitter')
|
||||
import nodeEmitter from './default-emitter.js'
|
||||
import redisEmitter from './redis-emitter.js'
|
||||
|
||||
let emitter
|
||||
|
||||
|
|
@ -8,7 +8,7 @@ let emitter
|
|||
* Used to transmit events (such as progress, upload completion) from controllers,
|
||||
* such as the Google Drive 'get' controller, along to the client.
|
||||
*/
|
||||
module.exports = (redisClient, redisPubSubScope) => {
|
||||
export default function getEmitter(redisClient, redisPubSubScope) {
|
||||
if (!emitter) {
|
||||
emitter = redisClient
|
||||
? redisEmitter(redisClient, redisPubSubScope)
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
const { EventEmitter } = require('node:events')
|
||||
const { default: safeStringify } = require('fast-safe-stringify')
|
||||
|
||||
const logger = require('../logger')
|
||||
import { EventEmitter } from 'node:events'
|
||||
import safeStringify from 'fast-safe-stringify'
|
||||
import * as logger from '../logger.js'
|
||||
|
||||
function replacer(key, value) {
|
||||
// Remove the circular structure and internal ones
|
||||
|
|
@ -17,7 +16,7 @@ function replacer(key, value) {
|
|||
* @param {string} redisPubSubScope
|
||||
* @returns
|
||||
*/
|
||||
module.exports = (redisClient, redisPubSubScope) => {
|
||||
export default function redisEmitter(redisClient, redisPubSubScope) {
|
||||
const prefix = redisPubSubScope ? `${redisPubSubScope}:` : ''
|
||||
const getPrefixedEventName = (eventName) => `${prefix}${eventName}`
|
||||
|
||||
|
|
@ -176,7 +175,7 @@ module.exports = (redisClient, redisPubSubScope) => {
|
|||
await runWhenConnected(async ({ publisher }) =>
|
||||
publisher.publish(
|
||||
getPrefixedEventName(eventName),
|
||||
safeStringify(args, replacer),
|
||||
safeStringify.default(args, replacer),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +0,0 @@
|
|||
const gotPromise = import('got')
|
||||
|
||||
module.exports = gotPromise.then((got) => got.default)
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
const logger = require('./logger')
|
||||
import * as logger from './logger.js'
|
||||
|
||||
/**
|
||||
* Forbidden header names.
|
||||
|
|
@ -49,7 +49,7 @@ const isForbiddenHeader = (header) => {
|
|||
return forbidden
|
||||
}
|
||||
|
||||
module.exports = (headers) => {
|
||||
export default function headerBlacklist(headers) {
|
||||
if (
|
||||
headers == null ||
|
||||
typeof headers !== 'object' ||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
const jwt = require('jsonwebtoken')
|
||||
const { encrypt, decrypt } = require('./utils')
|
||||
import jwt from 'jsonwebtoken'
|
||||
import { decrypt, encrypt } from './utils.js'
|
||||
|
||||
// The Uppy auth token is an encrypted JWT & JSON encoded container.
|
||||
// It used to simply contain an OAuth access_token and refresh_token for a specific provider.
|
||||
|
|
@ -17,13 +17,8 @@ const { encrypt, decrypt } = require('./utils')
|
|||
// even though the provider refresh token would still have been accepted and
|
||||
// there's no way for them to retry their failed files.
|
||||
// With 400 days, there's still a theoretical possibility but very low.
|
||||
const MAX_AGE_REFRESH_TOKEN = 60 * 60 * 24 * 400
|
||||
|
||||
const MAX_AGE_24H = 60 * 60 * 24
|
||||
|
||||
module.exports.MAX_AGE_24H = MAX_AGE_24H
|
||||
module.exports.MAX_AGE_REFRESH_TOKEN = MAX_AGE_REFRESH_TOKEN
|
||||
|
||||
export const MAX_AGE_REFRESH_TOKEN = 60 * 60 * 24 * 400
|
||||
export const MAX_AGE_24H = 60 * 60 * 24
|
||||
/**
|
||||
*
|
||||
* @param {*} data
|
||||
|
|
@ -49,7 +44,7 @@ const verifyToken = (token, secret) => {
|
|||
* @param {*} payload
|
||||
* @param {string} secret
|
||||
*/
|
||||
module.exports.generateEncryptedToken = (
|
||||
export const generateEncryptedToken = (
|
||||
payload,
|
||||
secret,
|
||||
maxAge = MAX_AGE_24H,
|
||||
|
|
@ -62,12 +57,8 @@ module.exports.generateEncryptedToken = (
|
|||
* @param {*} payload
|
||||
* @param {string} secret
|
||||
*/
|
||||
module.exports.generateEncryptedAuthToken = (payload, secret, maxAge) => {
|
||||
return module.exports.generateEncryptedToken(
|
||||
JSON.stringify(payload),
|
||||
secret,
|
||||
maxAge,
|
||||
)
|
||||
export const generateEncryptedAuthToken = (payload, secret, maxAge) => {
|
||||
return generateEncryptedToken(JSON.stringify(payload), secret, maxAge)
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -75,7 +66,7 @@ module.exports.generateEncryptedAuthToken = (payload, secret, maxAge) => {
|
|||
* @param {string} token
|
||||
* @param {string} secret
|
||||
*/
|
||||
module.exports.verifyEncryptedToken = (token, secret) => {
|
||||
export const verifyEncryptedToken = (token, secret) => {
|
||||
const ret = verifyToken(decrypt(token, secret), secret)
|
||||
if (!ret) throw new Error('No payload')
|
||||
return ret
|
||||
|
|
@ -86,8 +77,8 @@ module.exports.verifyEncryptedToken = (token, secret) => {
|
|||
* @param {string} token
|
||||
* @param {string} secret
|
||||
*/
|
||||
module.exports.verifyEncryptedAuthToken = (token, secret, providerName) => {
|
||||
const json = module.exports.verifyEncryptedToken(token, secret)
|
||||
export const verifyEncryptedAuthToken = (token, secret, providerName) => {
|
||||
const json = verifyEncryptedToken(token, secret)
|
||||
const tokens = JSON.parse(json)
|
||||
if (!tokens[providerName])
|
||||
throw new Error(`Missing token payload for provider ${providerName}`)
|
||||
|
|
@ -133,7 +124,7 @@ const addToCookies = ({
|
|||
res.cookie(getCookieName(oauthProvider), token, cookieOptions)
|
||||
}
|
||||
|
||||
module.exports.addToCookiesIfNeeded = (req, res, uppyAuthToken, maxAge) => {
|
||||
export const addToCookiesIfNeeded = (req, res, uppyAuthToken, maxAge) => {
|
||||
// some providers need the token in cookies for thumbnail/image requests
|
||||
if (req.companion.provider.needsCookieAuth) {
|
||||
addToCookies({
|
||||
|
|
@ -152,7 +143,7 @@ module.exports.addToCookiesIfNeeded = (req, res, uppyAuthToken, maxAge) => {
|
|||
* @param {object} companionOptions
|
||||
* @param {string} oauthProvider
|
||||
*/
|
||||
module.exports.removeFromCookies = (res, companionOptions, oauthProvider) => {
|
||||
export const removeFromCookies = (res, companionOptions, oauthProvider) => {
|
||||
// options must be identical to those given to res.cookie(), excluding expires and maxAge.
|
||||
// https://expressjs.com/en/api.html#res.clearCookie
|
||||
const cookieOptions = getCommonCookieOptions({ companionOptions })
|
||||
|
|
|
|||
|
|
@ -1,26 +1,26 @@
|
|||
const crypto = require('node:crypto')
|
||||
const { encrypt, decrypt } = require('./utils')
|
||||
import crypto from 'node:crypto'
|
||||
import { decrypt, encrypt } from './utils.js'
|
||||
|
||||
module.exports.encodeState = (state, secret) => {
|
||||
export const encodeState = (state, secret) => {
|
||||
const encodedState = Buffer.from(JSON.stringify(state)).toString('base64')
|
||||
return encrypt(encodedState, secret)
|
||||
}
|
||||
|
||||
module.exports.decodeState = (state, secret) => {
|
||||
export const decodeState = (state, secret) => {
|
||||
const encodedState = decrypt(state, secret)
|
||||
return JSON.parse(atob(encodedState))
|
||||
}
|
||||
|
||||
module.exports.generateState = () => {
|
||||
export const generateState = () => {
|
||||
return {
|
||||
id: crypto.randomBytes(10).toString('hex'),
|
||||
}
|
||||
}
|
||||
|
||||
module.exports.getFromState = (state, name, secret) => {
|
||||
return module.exports.decodeState(state, secret)[name]
|
||||
export const getFromState = (state, name, secret) => {
|
||||
return decodeState(state, secret)[name]
|
||||
}
|
||||
|
||||
module.exports.getGrantDynamicFromRequest = (req) => {
|
||||
export const getGrantDynamicFromRequest = (req) => {
|
||||
return req.session.grant?.dynamic ?? {}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,14 +1,13 @@
|
|||
const http = require('node:http')
|
||||
const https = require('node:https')
|
||||
const dns = require('node:dns')
|
||||
const ipaddr = require('ipaddr.js')
|
||||
const path = require('node:path')
|
||||
const contentDisposition = require('content-disposition')
|
||||
const validator = require('validator')
|
||||
import dns from 'node:dns'
|
||||
import http from 'node:http'
|
||||
import https from 'node:https'
|
||||
import path from 'node:path'
|
||||
import contentDisposition from 'content-disposition'
|
||||
import got from 'got'
|
||||
import ipaddr from 'ipaddr.js'
|
||||
import validator from 'validator'
|
||||
|
||||
const got = require('../got')
|
||||
|
||||
const FORBIDDEN_IP_ADDRESS = 'Forbidden IP address'
|
||||
export const FORBIDDEN_IP_ADDRESS = 'Forbidden IP address'
|
||||
|
||||
// Example scary IPs that should return false (ipv6-to-ipv4 mapped):
|
||||
// ::FFFF:127.0.0.1
|
||||
|
|
@ -16,8 +15,6 @@ const FORBIDDEN_IP_ADDRESS = 'Forbidden IP address'
|
|||
const isDisallowedIP = (ipAddress) =>
|
||||
ipaddr.parse(ipAddress).range() !== 'unicast'
|
||||
|
||||
module.exports.FORBIDDEN_IP_ADDRESS = FORBIDDEN_IP_ADDRESS
|
||||
|
||||
/**
|
||||
* Validates that the download URL is secure
|
||||
*
|
||||
|
|
@ -41,7 +38,7 @@ const validateURL = (url, allowLocalUrls) => {
|
|||
return true
|
||||
}
|
||||
|
||||
module.exports.validateURL = validateURL
|
||||
export { validateURL }
|
||||
|
||||
/**
|
||||
* Returns http Agent that will prevent requests to private IPs (to prevent SSRF)
|
||||
|
|
@ -100,9 +97,9 @@ const getProtectedHttpAgent = ({ protocol, allowLocalIPs }) => {
|
|||
}
|
||||
}
|
||||
|
||||
module.exports.getProtectedHttpAgent = getProtectedHttpAgent
|
||||
export { getProtectedHttpAgent }
|
||||
|
||||
async function getProtectedGot({ allowLocalIPs }) {
|
||||
function getProtectedGot({ allowLocalIPs }) {
|
||||
const HttpAgent = getProtectedHttpAgent({ protocol: 'http', allowLocalIPs })
|
||||
const HttpsAgent = getProtectedHttpAgent({
|
||||
protocol: 'https',
|
||||
|
|
@ -112,10 +109,10 @@ async function getProtectedGot({ allowLocalIPs }) {
|
|||
const httpsAgent = new HttpsAgent()
|
||||
|
||||
// @ts-ignore
|
||||
return (await got).extend({ agent: { http: httpAgent, https: httpsAgent } })
|
||||
return got.extend({ agent: { http: httpAgent, https: httpsAgent } })
|
||||
}
|
||||
|
||||
module.exports.getProtectedGot = getProtectedGot
|
||||
export { getProtectedGot }
|
||||
|
||||
/**
|
||||
* Gets the size and content type of a url's content
|
||||
|
|
@ -124,13 +121,13 @@ module.exports.getProtectedGot = getProtectedGot
|
|||
* @param {boolean} allowLocalIPs
|
||||
* @returns {Promise<{name: string, type: string, size: number}>}
|
||||
*/
|
||||
exports.getURLMeta = async (
|
||||
export async function getURLMeta(
|
||||
url,
|
||||
allowLocalIPs = false,
|
||||
options = undefined,
|
||||
) => {
|
||||
) {
|
||||
async function requestWithMethod(method) {
|
||||
const protectedGot = await getProtectedGot({ allowLocalIPs })
|
||||
const protectedGot = getProtectedGot({ allowLocalIPs })
|
||||
const stream = protectedGot.stream(url, {
|
||||
method,
|
||||
throwHttpErrors: false,
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
const Uploader = require('../Uploader')
|
||||
const logger = require('../logger')
|
||||
import logger from '../logger.js'
|
||||
import Uploader from '../Uploader.js'
|
||||
|
||||
async function startDownUpload({ req, res, getSize, download }) {
|
||||
export async function startDownUpload({ req, res, getSize, download }) {
|
||||
logger.debug('Starting download stream.', null, req.id)
|
||||
const { stream, size: maybeSize } = await download()
|
||||
|
||||
|
|
@ -48,5 +48,3 @@ async function startDownUpload({ req, res, getSize, download }) {
|
|||
// NOTE: the Uploader will continue running after the http request is responded
|
||||
res.status(200).json({ token: uploader.token })
|
||||
}
|
||||
|
||||
module.exports = { startDownUpload }
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
const crypto = require('node:crypto')
|
||||
import crypto from 'node:crypto'
|
||||
|
||||
const authTagLength = 16
|
||||
const nonceLength = 16
|
||||
|
|
@ -11,7 +11,7 @@ const ivLength = 12
|
|||
* @param {string[]} criteria
|
||||
* @returns {boolean}
|
||||
*/
|
||||
exports.hasMatch = (value, criteria) => {
|
||||
export const hasMatch = (value, criteria) => {
|
||||
return criteria.some((i) => {
|
||||
return value === i || new RegExp(i).test(value)
|
||||
})
|
||||
|
|
@ -22,7 +22,7 @@ exports.hasMatch = (value, criteria) => {
|
|||
* @param {object} data
|
||||
* @returns {string}
|
||||
*/
|
||||
exports.jsonStringify = (data) => {
|
||||
export const jsonStringify = (data) => {
|
||||
const cache = []
|
||||
return JSON.stringify(data, (key, value) => {
|
||||
if (typeof value === 'object' && value !== null) {
|
||||
|
|
@ -42,7 +42,7 @@ exports.jsonStringify = (data) => {
|
|||
*
|
||||
* @param {object} options companion options
|
||||
*/
|
||||
module.exports.getURLBuilder = (options) => {
|
||||
export function getURLBuilder(options) {
|
||||
/**
|
||||
* Builds companion targeted url
|
||||
*
|
||||
|
|
@ -73,7 +73,7 @@ module.exports.getURLBuilder = (options) => {
|
|||
return buildURL
|
||||
}
|
||||
|
||||
module.exports.getRedirectPath = (providerName) => `/${providerName}/redirect`
|
||||
export const getRedirectPath = (providerName) => `/${providerName}/redirect`
|
||||
|
||||
/**
|
||||
* Create an AES-CCM encryption key and initialization vector from the provided secret
|
||||
|
|
@ -104,7 +104,7 @@ function createSecrets(secret, nonce) {
|
|||
* @param {string|Buffer} secret
|
||||
* @returns {string} Ciphertext as a hex string, prefixed with 32 hex characters containing the iv.
|
||||
*/
|
||||
module.exports.encrypt = (input, secret) => {
|
||||
export const encrypt = (input, secret) => {
|
||||
const nonce = crypto.randomBytes(nonceLength)
|
||||
const { key, iv } = createSecrets(secret, nonce)
|
||||
const cipher = crypto.createCipheriv('aes-256-ccm', key, iv, {
|
||||
|
|
@ -126,7 +126,7 @@ module.exports.encrypt = (input, secret) => {
|
|||
* @param {string|Buffer} secret
|
||||
* @returns {string} Decrypted value.
|
||||
*/
|
||||
module.exports.decrypt = (encrypted, secret) => {
|
||||
export const decrypt = (encrypted, secret) => {
|
||||
const nonceHexLength = nonceLength * 2 // because hex encoding uses 2 bytes per byte
|
||||
|
||||
// NOTE: The first 32 characters are the nonce, in hex format.
|
||||
|
|
@ -164,14 +164,14 @@ module.exports.decrypt = (encrypted, secret) => {
|
|||
return decrypted.toString('utf8')
|
||||
}
|
||||
|
||||
module.exports.defaultGetKey = ({ filename }) => {
|
||||
export const defaultGetKey = ({ filename }) => {
|
||||
return `${crypto.randomUUID()}-${filename}`
|
||||
}
|
||||
|
||||
/**
|
||||
* Our own HttpError in cases where we can't use `got`'s `HTTPError`
|
||||
*/
|
||||
class HttpError extends Error {
|
||||
export class HttpError extends Error {
|
||||
statusCode
|
||||
|
||||
responseJson
|
||||
|
|
@ -184,9 +184,7 @@ class HttpError extends Error {
|
|||
}
|
||||
}
|
||||
|
||||
module.exports.HttpError = HttpError
|
||||
|
||||
module.exports.prepareStream = async (stream) =>
|
||||
export const prepareStream = async (stream) =>
|
||||
new Promise((resolve, reject) => {
|
||||
stream
|
||||
.on('response', (response) => {
|
||||
|
|
@ -229,7 +227,7 @@ module.exports.prepareStream = async (stream) =>
|
|||
})
|
||||
})
|
||||
|
||||
module.exports.getBasicAuthHeader = (key, secret) => {
|
||||
export const getBasicAuthHeader = (key, secret) => {
|
||||
const base64 = Buffer.from(`${key}:${secret}`, 'binary').toString('base64')
|
||||
return `Basic ${base64}`
|
||||
}
|
||||
|
|
@ -241,7 +239,7 @@ const rfc2047Encode = (dataIn) => {
|
|||
return `=?UTF-8?B?${Buffer.from(data).toString('base64')}?=` // We encode non-ASCII strings
|
||||
}
|
||||
|
||||
module.exports.rfc2047EncodeMetadata = (metadata) =>
|
||||
export const rfc2047EncodeMetadata = (metadata) =>
|
||||
Object.fromEntries(
|
||||
Object.entries(metadata).map((entry) => entry.map(rfc2047Encode)),
|
||||
)
|
||||
|
|
@ -260,7 +258,7 @@ module.exports.rfc2047EncodeMetadata = (metadata) =>
|
|||
* }} param0
|
||||
* @returns
|
||||
*/
|
||||
module.exports.getBucket = ({ bucketOrFn, req, metadata, filename }) => {
|
||||
export const getBucket = ({ bucketOrFn, req, metadata, filename }) => {
|
||||
const bucket =
|
||||
typeof bucketOrFn === 'function'
|
||||
? bucketOrFn({ req, metadata, filename })
|
||||
|
|
@ -282,6 +280,6 @@ module.exports.getBucket = ({ bucketOrFn, req, metadata, filename }) => {
|
|||
* @param {number} maxFilenameLength
|
||||
* @returns {string}
|
||||
*/
|
||||
module.exports.truncateFilename = (filename, maxFilenameLength) => {
|
||||
export const truncateFilename = (filename, maxFilenameLength) => {
|
||||
return filename.slice(maxFilenameLength * -1)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,12 +1,10 @@
|
|||
const schedule = require('node-schedule')
|
||||
const fs = require('node:fs')
|
||||
const path = require('node:path')
|
||||
const { setTimeout: sleep } = require('node:timers/promises')
|
||||
|
||||
const got = require('./got')
|
||||
|
||||
const { FILE_NAME_PREFIX } = require('./Uploader')
|
||||
const logger = require('./logger')
|
||||
import fs from 'node:fs'
|
||||
import path from 'node:path'
|
||||
import { setTimeout as sleep } from 'node:timers/promises'
|
||||
import got from 'got'
|
||||
import schedule from 'node-schedule'
|
||||
import * as logger from './logger.js'
|
||||
import Uploader from './Uploader.js'
|
||||
|
||||
const cleanUpFinishedUploads = (dirPath) => {
|
||||
logger.info(
|
||||
|
|
@ -24,7 +22,7 @@ const cleanUpFinishedUploads = (dirPath) => {
|
|||
// if it does not contain FILE_NAME_PREFIX then it probably wasn't created by companion.
|
||||
// this is to avoid deleting unintended files, e.g if a wrong path was accidentally given
|
||||
// by a developer.
|
||||
if (!file.startsWith(FILE_NAME_PREFIX)) {
|
||||
if (!file.startsWith(Uploader.FILE_NAME_PREFIX)) {
|
||||
logger.info(`skipping file ${file}`, 'jobs.cleanup.skip')
|
||||
return
|
||||
}
|
||||
|
|
@ -56,7 +54,7 @@ const cleanUpFinishedUploads = (dirPath) => {
|
|||
*
|
||||
* @param {string} dirPath path to the directory which you want to clean
|
||||
*/
|
||||
exports.startCleanUpJob = (dirPath) => {
|
||||
export function startCleanUpJob(dirPath) {
|
||||
logger.info('starting clean up job', 'jobs.cleanup.start')
|
||||
// run once a day
|
||||
schedule.scheduleJob('0 23 * * *', () => cleanUpFinishedUploads(dirPath))
|
||||
|
|
@ -67,7 +65,7 @@ async function runPeriodicPing({ urls, payload, requestTimeout }) {
|
|||
await Promise.all(
|
||||
urls.map(async (url) => {
|
||||
try {
|
||||
await (await got).post(url, {
|
||||
await got.post(url, {
|
||||
json: payload,
|
||||
timeout: { request: requestTimeout },
|
||||
})
|
||||
|
|
@ -80,14 +78,14 @@ async function runPeriodicPing({ urls, payload, requestTimeout }) {
|
|||
|
||||
// This function is used to start a periodic POST request against a user-defined URL
|
||||
// or set of URLs, for example as a watch dog health check.
|
||||
exports.startPeriodicPingJob = async ({
|
||||
export async function startPeriodicPingJob({
|
||||
urls,
|
||||
interval = 60000,
|
||||
count,
|
||||
staticPayload = {},
|
||||
version,
|
||||
processId,
|
||||
}) => {
|
||||
}) {
|
||||
if (urls.length === 0) return
|
||||
|
||||
logger.info('Starting periodic ping job', 'jobs.periodic.ping.start')
|
||||
|
|
|
|||
|
|
@ -1,20 +1,16 @@
|
|||
const escapeStringRegexp = require('escape-string-regexp')
|
||||
const util = require('node:util')
|
||||
const supportsColors = require('supports-color')
|
||||
import util from 'node:util'
|
||||
import escapeStringRegexp from 'escape-string-regexp'
|
||||
import supportsColors from 'supports-color'
|
||||
|
||||
const valuesToMask = []
|
||||
let valuesToMask = []
|
||||
/**
|
||||
* Adds a list of strings that should be masked by the logger.
|
||||
* This function can only be called once through out the life of the server.
|
||||
*
|
||||
* @param {Array} maskables a list of strings to be masked
|
||||
*/
|
||||
exports.setMaskables = (maskables) => {
|
||||
maskables.forEach((i) => {
|
||||
valuesToMask.push(escapeStringRegexp(i))
|
||||
})
|
||||
|
||||
Object.freeze(valuesToMask)
|
||||
export function setMaskables(maskables) {
|
||||
valuesToMask = maskables.map((i) => escapeStringRegexp(i))
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -34,7 +30,7 @@ function maskMessage(msg) {
|
|||
|
||||
let processName = 'companion'
|
||||
|
||||
exports.setProcessName = (newProcessName) => {
|
||||
export function setProcessName(newProcessName) {
|
||||
processName = newProcessName
|
||||
}
|
||||
|
||||
|
|
@ -90,7 +86,7 @@ const log = ({ arg, tag = '', level, traceId = '', color = [] }) => {
|
|||
* @param {string} [tag] a unique tag to easily search for this message
|
||||
* @param {string} [traceId] a unique id to easily trace logs tied to a request
|
||||
*/
|
||||
exports.info = (msg, tag, traceId) => {
|
||||
export function info(msg, tag, traceId) {
|
||||
log({ arg: msg, tag, level: 'info', traceId })
|
||||
}
|
||||
|
||||
|
|
@ -101,7 +97,7 @@ exports.info = (msg, tag, traceId) => {
|
|||
* @param {string} [tag] a unique tag to easily search for this message
|
||||
* @param {string} [traceId] a unique id to easily trace logs tied to a request
|
||||
*/
|
||||
exports.warn = (msg, tag, traceId) => {
|
||||
export function warn(msg, tag, traceId) {
|
||||
log({ arg: msg, tag, level: 'warn', traceId, color: ['bold', 'yellow'] })
|
||||
}
|
||||
|
||||
|
|
@ -112,7 +108,7 @@ exports.warn = (msg, tag, traceId) => {
|
|||
* @param {string} [tag] a unique tag to easily search for this message
|
||||
* @param {string} [traceId] a unique id to easily trace logs tied to a request
|
||||
*/
|
||||
exports.error = (msg, tag, traceId) => {
|
||||
export function error(msg, tag, traceId) {
|
||||
log({ arg: msg, tag, level: 'error', traceId, color: ['bold', 'red'] })
|
||||
}
|
||||
|
||||
|
|
@ -123,8 +119,11 @@ exports.error = (msg, tag, traceId) => {
|
|||
* @param {string} [tag] a unique tag to easily search for this message
|
||||
* @param {string} [traceId] a unique id to easily trace logs tied to a request
|
||||
*/
|
||||
exports.debug = (msg, tag, traceId) => {
|
||||
export function debug(msg, tag, traceId) {
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
log({ arg: msg, tag, level: 'debug', traceId, color: ['bold', 'blue'] })
|
||||
}
|
||||
}
|
||||
|
||||
const logger = { setMaskables, setProcessName, info, warn, error, debug }
|
||||
export default logger
|
||||
|
|
|
|||
|
|
@ -1,15 +1,14 @@
|
|||
const cors = require('cors')
|
||||
const promBundle = require('express-prom-bundle')
|
||||
import corsImport from 'cors'
|
||||
import promBundle from 'express-prom-bundle'
|
||||
|
||||
// @ts-ignore
|
||||
const { version } = require('../../package.json')
|
||||
const tokenService = require('./helpers/jwt')
|
||||
const logger = require('./logger')
|
||||
const getS3Client = require('./s3-client')
|
||||
const { getURLBuilder } = require('./helpers/utils')
|
||||
const { isOAuthProvider } = require('./provider/Provider')
|
||||
import packageJson from '../../package.json' with { type: 'json' }
|
||||
import * as tokenService from './helpers/jwt.js'
|
||||
import { getURLBuilder } from './helpers/utils.js'
|
||||
import * as logger from './logger.js'
|
||||
import { isOAuthProvider } from './provider/Provider.js'
|
||||
import getS3Client from './s3-client.js'
|
||||
|
||||
exports.hasSessionAndProvider = (req, res, next) => {
|
||||
export const hasSessionAndProvider = (req, res, next) => {
|
||||
if (!req.session) {
|
||||
logger.debug(
|
||||
'No session attached to req object. Exiting dispatcher.',
|
||||
|
|
@ -40,7 +39,7 @@ const isSimpleAuthProviderReq = (req) =>
|
|||
* Middleware can be used to verify that the current request is to an OAuth provider
|
||||
* This is because not all requests are supported by non-oauth providers (formerly known as SearchProviders)
|
||||
*/
|
||||
exports.hasOAuthProvider = (req, res, next) => {
|
||||
export const hasOAuthProvider = (req, res, next) => {
|
||||
if (!isOAuthProviderReq(req)) {
|
||||
logger.debug('Provider does not support OAuth.', null, req.id)
|
||||
return res.sendStatus(400)
|
||||
|
|
@ -49,7 +48,7 @@ exports.hasOAuthProvider = (req, res, next) => {
|
|||
return next()
|
||||
}
|
||||
|
||||
exports.hasSimpleAuthProvider = (req, res, next) => {
|
||||
export const hasSimpleAuthProvider = (req, res, next) => {
|
||||
if (!isSimpleAuthProviderReq(req)) {
|
||||
logger.debug('Provider does not support simple auth.', null, req.id)
|
||||
return res.sendStatus(400)
|
||||
|
|
@ -58,7 +57,7 @@ exports.hasSimpleAuthProvider = (req, res, next) => {
|
|||
return next()
|
||||
}
|
||||
|
||||
exports.hasBody = (req, res, next) => {
|
||||
export const hasBody = (req, res, next) => {
|
||||
if (!req.body) {
|
||||
logger.debug(
|
||||
'No body attached to req object. Exiting dispatcher.',
|
||||
|
|
@ -71,7 +70,7 @@ exports.hasBody = (req, res, next) => {
|
|||
return next()
|
||||
}
|
||||
|
||||
exports.hasSearchQuery = (req, res, next) => {
|
||||
export const hasSearchQuery = (req, res, next) => {
|
||||
if (typeof req.query.q !== 'string') {
|
||||
logger.debug(
|
||||
'search request has no search query',
|
||||
|
|
@ -84,7 +83,7 @@ exports.hasSearchQuery = (req, res, next) => {
|
|||
return next()
|
||||
}
|
||||
|
||||
exports.verifyToken = (req, res, next) => {
|
||||
export const verifyToken = (req, res, next) => {
|
||||
if (isOAuthProviderReq(req) || isSimpleAuthProviderReq(req)) {
|
||||
// For OAuth / simple auth provider, we find the encrypted auth token from the header:
|
||||
const token = req.companion.authToken
|
||||
|
|
@ -133,7 +132,7 @@ exports.verifyToken = (req, res, next) => {
|
|||
}
|
||||
|
||||
// does not fail if token is invalid
|
||||
exports.gentleVerifyToken = (req, res, next) => {
|
||||
export const gentleVerifyToken = (req, res, next) => {
|
||||
const { providerName } = req.params
|
||||
if (req.companion.authToken) {
|
||||
try {
|
||||
|
|
@ -150,13 +149,13 @@ exports.gentleVerifyToken = (req, res, next) => {
|
|||
next()
|
||||
}
|
||||
|
||||
exports.cookieAuthToken = (req, res, next) => {
|
||||
export const cookieAuthToken = (req, res, next) => {
|
||||
req.companion.authToken =
|
||||
req.cookies[`uppyAuthToken--${req.companion.providerClass.oauthProvider}`]
|
||||
return next()
|
||||
}
|
||||
|
||||
exports.cors =
|
||||
export const cors =
|
||||
(options = {}) =>
|
||||
(req, res, next) => {
|
||||
// HTTP headers are not case sensitive, and express always handles them in lower case, so that's why we lower case them.
|
||||
|
|
@ -207,7 +206,7 @@ exports.cors =
|
|||
const { corsOrigins: origin = true } = options
|
||||
|
||||
// Because we need to merge with existing headers, we need to call cors inside our own middleware
|
||||
return cors({
|
||||
return corsImport({
|
||||
credentials: true,
|
||||
origin,
|
||||
methods: Array.from(allowMethodsSet),
|
||||
|
|
@ -216,7 +215,7 @@ exports.cors =
|
|||
})(req, res, next)
|
||||
}
|
||||
|
||||
exports.metrics = ({ path = undefined } = {}) => {
|
||||
export const metrics = ({ path = undefined } = {}) => {
|
||||
const metricsMiddleware = promBundle({
|
||||
includeMethod: true,
|
||||
metricsPath: path ? `${path}/metrics` : undefined,
|
||||
|
|
@ -231,7 +230,7 @@ exports.metrics = ({ path = undefined } = {}) => {
|
|||
name: 'companion_version',
|
||||
help: 'npm version as an integer',
|
||||
})
|
||||
const numberVersion = Number(version.replace(/\D/g, ''))
|
||||
const numberVersion = Number(packageJson.version.replace(/\D/g, ''))
|
||||
versionGauge.set(numberVersion)
|
||||
return metricsMiddleware
|
||||
}
|
||||
|
|
@ -240,7 +239,7 @@ exports.metrics = ({ path = undefined } = {}) => {
|
|||
*
|
||||
* @param {object} options
|
||||
*/
|
||||
exports.getCompanionMiddleware = (options) => {
|
||||
export const getCompanionMiddleware = (options) => {
|
||||
/**
|
||||
* @param {object} req
|
||||
* @param {object} res
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
const { MAX_AGE_24H } = require('../helpers/jwt')
|
||||
import { MAX_AGE_24H } from '../helpers/jwt.js'
|
||||
|
||||
/**
|
||||
* Provider interface defines the specifications of any provider implementation
|
||||
*/
|
||||
class Provider {
|
||||
export default class Provider {
|
||||
/**
|
||||
*
|
||||
* @param {{providerName: string, allowLocalUrls: boolean, providerGrantConfig?: object, secret: string}} options
|
||||
|
|
@ -114,7 +114,6 @@ class Provider {
|
|||
}
|
||||
}
|
||||
|
||||
module.exports = Provider
|
||||
// OAuth providers are those that have an `oauthProvider` set. It means they require OAuth authentication to work
|
||||
module.exports.isOAuthProvider = (oauthProvider) =>
|
||||
export const isOAuthProvider = (oauthProvider) =>
|
||||
typeof oauthProvider === 'string' && oauthProvider.length > 0
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
const mime = require('mime-types')
|
||||
const querystring = require('node:querystring')
|
||||
import querystring from 'node:querystring'
|
||||
import mime from 'mime-types'
|
||||
|
||||
const isFolder = (item) => {
|
||||
return item.type === 'folder'
|
||||
|
|
@ -52,7 +52,7 @@ const getNextPagePath = (data) => {
|
|||
return `?${querystring.stringify(query)}`
|
||||
}
|
||||
|
||||
module.exports = function adaptData(res, username, companion) {
|
||||
const adaptData = function adaptData(res, username, companion) {
|
||||
const data = { username, items: [] }
|
||||
const items = getItemSubList(res)
|
||||
items.forEach((item) => {
|
||||
|
|
@ -73,3 +73,5 @@ module.exports = function adaptData(res, username, companion) {
|
|||
|
||||
return data
|
||||
}
|
||||
|
||||
export default adaptData
|
||||
|
|
|
|||
|
|
@ -1,15 +1,14 @@
|
|||
const Provider = require('../Provider')
|
||||
const adaptData = require('./adapter')
|
||||
const { withProviderErrorHandling } = require('../providerErrors')
|
||||
const { prepareStream } = require('../../helpers/utils')
|
||||
|
||||
const got = require('../../got')
|
||||
import got from 'got'
|
||||
import { prepareStream } from '../../helpers/utils.js'
|
||||
import Provider from '../Provider.js'
|
||||
import { withProviderErrorHandling } from '../providerErrors.js'
|
||||
import adaptData from './adapter.js'
|
||||
|
||||
const BOX_FILES_FIELDS = 'id,modified_at,name,permissions,size,type'
|
||||
const BOX_THUMBNAIL_SIZE = 256
|
||||
|
||||
const getClient = async ({ token }) =>
|
||||
(await got).extend({
|
||||
const getClient = ({ token }) =>
|
||||
got.extend({
|
||||
prefixUrl: 'https://api.box.com/2.0',
|
||||
headers: {
|
||||
authorization: `Bearer ${token}`,
|
||||
|
|
@ -17,14 +16,12 @@ const getClient = async ({ token }) =>
|
|||
})
|
||||
|
||||
async function getUserInfo({ token }) {
|
||||
return (await getClient({ token }))
|
||||
.get('users/me', { responseType: 'json' })
|
||||
.json()
|
||||
return getClient({ token }).get('users/me', { responseType: 'json' }).json()
|
||||
}
|
||||
|
||||
async function list({ directory, query, token }) {
|
||||
const rootFolderID = '0'
|
||||
return (await getClient({ token }))
|
||||
return getClient({ token })
|
||||
.get(`folders/${directory || rootFolderID}/items`, {
|
||||
searchParams: {
|
||||
fields: BOX_FILES_FIELDS,
|
||||
|
|
@ -39,7 +36,7 @@ async function list({ directory, query, token }) {
|
|||
/**
|
||||
* Adapter for API https://developer.box.com/reference/
|
||||
*/
|
||||
class Box extends Provider {
|
||||
export default class Box extends Provider {
|
||||
constructor(options) {
|
||||
super(options)
|
||||
// needed for the thumbnails fetched via companion
|
||||
|
|
@ -77,10 +74,9 @@ class Box extends Provider {
|
|||
|
||||
async download({ id, providerUserSession: { accessToken: token } }) {
|
||||
return this.#withErrorHandling('provider.box.download.error', async () => {
|
||||
const stream = (await getClient({ token })).stream.get(
|
||||
`files/${id}/content`,
|
||||
{ responseType: 'json' },
|
||||
)
|
||||
const stream = getClient({ token }).stream.get(`files/${id}/content`, {
|
||||
responseType: 'json',
|
||||
})
|
||||
|
||||
const { size } = await prepareStream(stream)
|
||||
return { stream, size }
|
||||
|
|
@ -100,7 +96,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 = (await getClient({ token })).stream.get(
|
||||
const stream = getClient({ token }).stream.get(
|
||||
`files/${id}/thumbnail.${extension}`,
|
||||
{
|
||||
searchParams: {
|
||||
|
|
@ -118,7 +114,7 @@ class Box extends Provider {
|
|||
|
||||
async size({ id, providerUserSession: { accessToken: token } }) {
|
||||
return this.#withErrorHandling('provider.box.size.error', async () => {
|
||||
const { size } = await (await getClient({ token }))
|
||||
const { size } = await getClient({ token })
|
||||
.get(`files/${id}`, { responseType: 'json' })
|
||||
.json()
|
||||
return parseInt(size, 10)
|
||||
|
|
@ -128,7 +124,7 @@ class Box extends Provider {
|
|||
logout({ companion, providerUserSession: { accessToken: token } }) {
|
||||
return this.#withErrorHandling('provider.box.logout.error', async () => {
|
||||
const { key, secret } = companion.options.providerOptions.box
|
||||
await (await getClient({ token })).post('oauth2/revoke', {
|
||||
await getClient({ token }).post('oauth2/revoke', {
|
||||
prefixUrl: 'https://api.box.com',
|
||||
form: {
|
||||
client_id: key,
|
||||
|
|
@ -152,5 +148,3 @@ class Box extends Provider {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Box
|
||||
|
|
|
|||
|
|
@ -1,12 +1,10 @@
|
|||
const { htmlEscape } = require('escape-goat')
|
||||
const logger = require('../logger')
|
||||
const oAuthState = require('../helpers/oauth-state')
|
||||
const tokenService = require('../helpers/jwt')
|
||||
const { getURLBuilder, getRedirectPath } = require('../helpers/utils')
|
||||
// biome-ignore lint/correctness/noUnusedVariables: used in types
|
||||
const Provider = require('./Provider')
|
||||
|
||||
const got = require('../got')
|
||||
import { htmlEscape } from 'escape-goat'
|
||||
import got from 'got'
|
||||
import * as tokenService from '../helpers/jwt.js'
|
||||
import * as oAuthState from '../helpers/oauth-state.js'
|
||||
import { getRedirectPath, getURLBuilder } from '../helpers/utils.js'
|
||||
import logger from '../logger.js'
|
||||
import Provider from './Provider.js'
|
||||
|
||||
/**
|
||||
* @param {string} url
|
||||
|
|
@ -15,7 +13,7 @@ const got = require('../got')
|
|||
*/
|
||||
async function fetchKeys(url, providerName, credentialRequestParams) {
|
||||
try {
|
||||
const { credentials } = await (await got)
|
||||
const { credentials } = await got
|
||||
.post(url, {
|
||||
json: { provider: providerName, parameters: credentialRequestParams },
|
||||
})
|
||||
|
|
@ -79,7 +77,10 @@ async function fetchProviderKeys(
|
|||
* @param {object} companionOptions companion options object
|
||||
* @returns {import('express').RequestHandler}
|
||||
*/
|
||||
exports.getCredentialsOverrideMiddleware = (providers, companionOptions) => {
|
||||
export const getCredentialsOverrideMiddleware = (
|
||||
providers,
|
||||
companionOptions,
|
||||
) => {
|
||||
return async (req, res, next) => {
|
||||
try {
|
||||
const { oauthProvider, override } = req.params
|
||||
|
|
@ -215,11 +216,7 @@ exports.getCredentialsOverrideMiddleware = (providers, companionOptions) => {
|
|||
* @param {object} req the express request object for the said request
|
||||
* @returns {(providerName: string, companionOptions: object, credentialRequestParams?: object) => Promise}
|
||||
*/
|
||||
module.exports.getCredentialsResolver = (
|
||||
providerName,
|
||||
companionOptions,
|
||||
req,
|
||||
) => {
|
||||
export const getCredentialsResolver = (providerName, companionOptions, req) => {
|
||||
const credentialsResolver = () => {
|
||||
const encodedCredentialsParams = req.header('uppy-credentials-params')
|
||||
let credentialRequestParams = null
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
const mime = require('mime-types')
|
||||
const querystring = require('node:querystring')
|
||||
import querystring from 'node:querystring'
|
||||
import mime from 'mime-types'
|
||||
|
||||
const isFolder = (item) => {
|
||||
return item['.tag'] === 'folder'
|
||||
|
|
@ -49,7 +49,7 @@ const getNextPagePath = (data) => {
|
|||
return `?${querystring.stringify(query)}`
|
||||
}
|
||||
|
||||
module.exports = (res, email, buildURL) => {
|
||||
const adaptData = (res, email, buildURL) => {
|
||||
const items = getItemSubList(res).map((item) => ({
|
||||
isFolder: isFolder(item),
|
||||
icon: getItemIcon(item),
|
||||
|
|
@ -69,3 +69,5 @@ module.exports = (res, email, buildURL) => {
|
|||
nextPagePath: getNextPagePath(res),
|
||||
}
|
||||
}
|
||||
|
||||
export default adaptData
|
||||
|
|
|
|||
|
|
@ -1,16 +1,15 @@
|
|||
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 logger = require('../../logger')
|
||||
|
||||
const gotPromise = require('../../got')
|
||||
|
||||
// From https://www.dropbox.com/developers/reference/json-encoding:
|
||||
//
|
||||
// This function is simple and has OK performance compared to more
|
||||
// complicated ones: http://jsperf.com/json-escape-unicode/4
|
||||
import got from 'got'
|
||||
import { MAX_AGE_REFRESH_TOKEN } from '../../helpers/jwt.js'
|
||||
import { prepareStream } from '../../helpers/utils.js'
|
||||
import logger from '../../logger.js'
|
||||
import Provider from '../Provider.js'
|
||||
import { withProviderErrorHandling } from '../providerErrors.js'
|
||||
import adaptData from './adapter.js'
|
||||
|
||||
const charsToEncode = /[\u007f-\uffff]/g
|
||||
function httpHeaderSafeJson(v) {
|
||||
return JSON.stringify(v).replace(charsToEncode, (c) => {
|
||||
|
|
@ -25,8 +24,6 @@ async function getUserInfo({ client }) {
|
|||
}
|
||||
|
||||
async function getClient({ token, namespaced }) {
|
||||
const got = await gotPromise
|
||||
|
||||
const makeClient = (namespace) =>
|
||||
got.extend({
|
||||
prefixUrl: 'https://api.dropboxapi.com/2',
|
||||
|
|
@ -70,8 +67,8 @@ async function getClient({ token, namespaced }) {
|
|||
}
|
||||
}
|
||||
|
||||
const getOauthClient = async () =>
|
||||
(await gotPromise).extend({
|
||||
const getOauthClient = () =>
|
||||
got.extend({
|
||||
prefixUrl: 'https://api.dropboxapi.com/oauth2',
|
||||
})
|
||||
|
||||
|
|
@ -102,7 +99,7 @@ async function list({ client, directory, query }) {
|
|||
/**
|
||||
* Adapter for API https://www.dropbox.com/developers/documentation/http/documentation
|
||||
*/
|
||||
class DropBox extends Provider {
|
||||
export default class Dropbox extends Provider {
|
||||
constructor(options) {
|
||||
super(options)
|
||||
this.needsCookieAuth = true
|
||||
|
|
@ -211,7 +208,7 @@ class DropBox extends Provider {
|
|||
return this.#withErrorHandling(
|
||||
'provider.dropbox.token.refresh.error',
|
||||
async () => {
|
||||
const { access_token: accessToken } = await (await getOauthClient())
|
||||
const { access_token: accessToken } = await getOauthClient()
|
||||
.post('token', {
|
||||
form: {
|
||||
refresh_token: refreshToken,
|
||||
|
|
@ -230,11 +227,9 @@ class DropBox extends Provider {
|
|||
return withProviderErrorHandling({
|
||||
fn,
|
||||
tag,
|
||||
providerName: DropBox.oauthProvider,
|
||||
providerName: Dropbox.oauthProvider,
|
||||
isAuthError: (response) => response.statusCode === 401,
|
||||
getJsonErrorMessage: (body) => body?.error_summary,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = DropBox
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
* ProviderApiError is error returned when an adapter encounters
|
||||
* an http error while communication with its corresponding provider
|
||||
*/
|
||||
class ProviderApiError extends Error {
|
||||
export class ProviderApiError extends Error {
|
||||
/**
|
||||
* @param {string} message error message
|
||||
* @param {number} statusCode the http status code from the provider api
|
||||
|
|
@ -15,7 +15,7 @@ class ProviderApiError extends Error {
|
|||
}
|
||||
}
|
||||
|
||||
class ProviderUserError extends ProviderApiError {
|
||||
export class ProviderUserError extends ProviderApiError {
|
||||
/**
|
||||
* @param {object} json arbitrary JSON.stringify-able object that will be passed to the client
|
||||
*/
|
||||
|
|
@ -32,7 +32,7 @@ class ProviderUserError extends ProviderApiError {
|
|||
* this signals to the client that the access token is invalid and needs to be
|
||||
* refreshed or the user needs to re-authenticate
|
||||
*/
|
||||
class ProviderAuthError extends ProviderApiError {
|
||||
export class ProviderAuthError extends ProviderApiError {
|
||||
constructor() {
|
||||
super('invalid access token detected by Provider', 401)
|
||||
this.name = 'AuthError'
|
||||
|
|
@ -40,7 +40,7 @@ class ProviderAuthError extends ProviderApiError {
|
|||
}
|
||||
}
|
||||
|
||||
function parseHttpError(err) {
|
||||
export function parseHttpError(err) {
|
||||
if (err?.name === 'HTTPError') {
|
||||
return {
|
||||
statusCode: err.response?.statusCode,
|
||||
|
|
@ -108,7 +108,7 @@ function errorToResponse(err) {
|
|||
return undefined
|
||||
}
|
||||
|
||||
function respondWithError(err, res) {
|
||||
export function respondWithError(err, res) {
|
||||
const errResp = errorToResponse(err)
|
||||
if (errResp) {
|
||||
res.status(errResp.code).json(errResp.json)
|
||||
|
|
@ -116,11 +116,3 @@ function respondWithError(err, res) {
|
|||
}
|
||||
return false
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
ProviderAuthError,
|
||||
ProviderApiError,
|
||||
ProviderUserError,
|
||||
respondWithError,
|
||||
parseHttpError,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
const querystring = require('node:querystring')
|
||||
import querystring from 'node:querystring'
|
||||
|
||||
const isFolder = (item) => {
|
||||
return !!item.type
|
||||
}
|
||||
|
||||
exports.sortImages = (images) => {
|
||||
export const sortImages = (images) => {
|
||||
// sort in ascending order of dimension
|
||||
return images.slice().sort((a, b) => a.width - b.width)
|
||||
}
|
||||
|
|
@ -13,7 +13,7 @@ const getItemIcon = (item) => {
|
|||
if (isFolder(item)) {
|
||||
return 'folder'
|
||||
}
|
||||
return exports.sortImages(item.images)[0].source
|
||||
return sortImages(item.images)[0].source
|
||||
}
|
||||
|
||||
const getItemSubList = (item) => {
|
||||
|
|
@ -41,7 +41,7 @@ const getItemModifiedDate = (item) => {
|
|||
}
|
||||
|
||||
const getItemThumbnailUrl = (item) => {
|
||||
return isFolder(item) ? null : exports.sortImages(item.images)[0].source
|
||||
return isFolder(item) ? null : sortImages(item.images)[0].source
|
||||
}
|
||||
|
||||
const getNextPagePath = (data, currentQuery, currentPath) => {
|
||||
|
|
@ -53,7 +53,7 @@ const getNextPagePath = (data, currentQuery, currentPath) => {
|
|||
return `${currentPath || ''}?${querystring.stringify(query)}`
|
||||
}
|
||||
|
||||
exports.adaptData = (res, username, directory, currentQuery) => {
|
||||
export const adaptData = (res, username, directory, currentQuery) => {
|
||||
const data = { username, items: [] }
|
||||
const items = getItemSubList(res)
|
||||
items.forEach((item) => {
|
||||
|
|
|
|||
|
|
@ -1,13 +1,10 @@
|
|||
const crypto = require('node:crypto')
|
||||
|
||||
const Provider = require('../Provider')
|
||||
const logger = require('../../logger')
|
||||
const { adaptData, sortImages } = require('./adapter')
|
||||
const { withProviderErrorHandling } = require('../providerErrors')
|
||||
const { prepareStream } = require('../../helpers/utils')
|
||||
const { HttpError } = require('../../helpers/utils')
|
||||
|
||||
const got = require('../../got')
|
||||
import crypto from 'node:crypto'
|
||||
import got from 'got'
|
||||
import { HttpError, prepareStream } from '../../helpers/utils.js'
|
||||
import logger from '../../logger.js'
|
||||
import Provider from '../Provider.js'
|
||||
import { withProviderErrorHandling } from '../providerErrors.js'
|
||||
import { adaptData, sortImages } from './adapter.js'
|
||||
|
||||
async function runRequestBatch({ secret, token, requests }) {
|
||||
// https://developers.facebook.com/docs/facebook-login/security/#appsecret
|
||||
|
|
@ -26,7 +23,7 @@ async function runRequestBatch({ secret, token, requests }) {
|
|||
batch: JSON.stringify(requests),
|
||||
}
|
||||
|
||||
const responsesRaw = await (await got)
|
||||
const responsesRaw = await got
|
||||
.post('https://graph.facebook.com', { form })
|
||||
.json()
|
||||
|
||||
|
|
@ -65,7 +62,7 @@ async function getMediaUrl({ secret, token, id }) {
|
|||
/**
|
||||
* Adapter for API https://developers.facebook.com/docs/graph-api/using-graph-api/
|
||||
*/
|
||||
class Facebook extends Provider {
|
||||
export default class Facebook extends Provider {
|
||||
static get oauthProvider() {
|
||||
return 'facebook'
|
||||
}
|
||||
|
|
@ -109,7 +106,7 @@ class Facebook extends Provider {
|
|||
'provider.facebook.download.error',
|
||||
async () => {
|
||||
const url = await getMediaUrl({ secret: this.secret, token, id })
|
||||
const stream = (await got).stream.get(url, { responseType: 'json' })
|
||||
const stream = got.stream.get(url, { responseType: 'json' })
|
||||
const { size } = await prepareStream(stream)
|
||||
return { stream, size }
|
||||
},
|
||||
|
|
@ -151,5 +148,3 @@ class Facebook extends Provider {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Facebook
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
const querystring = require('node:querystring')
|
||||
import querystring from 'node:querystring'
|
||||
|
||||
const getUsername = (data) => {
|
||||
return data.user.emailAddress
|
||||
}
|
||||
|
||||
exports.isGsuiteFile = (mimeType) => {
|
||||
export const isGsuiteFile = (mimeType) => {
|
||||
return mimeType?.startsWith('application/vnd.google')
|
||||
}
|
||||
|
||||
|
|
@ -19,7 +19,7 @@ const isFolder = (item) => {
|
|||
)
|
||||
}
|
||||
|
||||
exports.isShortcut = (mimeType) => {
|
||||
export const isShortcut = (mimeType) => {
|
||||
return mimeType === 'application/vnd.google-apps.shortcut'
|
||||
}
|
||||
|
||||
|
|
@ -60,7 +60,7 @@ const getItemSubList = (item) => {
|
|||
return item.files.filter((i) => {
|
||||
return (
|
||||
isFolder(i) ||
|
||||
!exports.isGsuiteFile(i.mimeType) ||
|
||||
!isGsuiteFile(i.mimeType) ||
|
||||
allowedGSuiteTypes.includes(i.mimeType)
|
||||
)
|
||||
})
|
||||
|
|
@ -83,7 +83,7 @@ const getItemName = (item) => {
|
|||
return item.name ? item.name : '/'
|
||||
}
|
||||
|
||||
exports.getGsuiteExportType = (mimeType) => {
|
||||
export const getGsuiteExportType = (mimeType) => {
|
||||
const typeMaps = {
|
||||
'application/vnd.google-apps.document':
|
||||
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
||||
|
|
@ -100,14 +100,14 @@ exports.getGsuiteExportType = (mimeType) => {
|
|||
}
|
||||
|
||||
function getMimeType2(mimeType) {
|
||||
if (exports.isGsuiteFile(mimeType)) {
|
||||
return exports.getGsuiteExportType(mimeType)
|
||||
if (isGsuiteFile(mimeType)) {
|
||||
return getGsuiteExportType(mimeType)
|
||||
}
|
||||
return mimeType
|
||||
}
|
||||
|
||||
const getMimeType = (item) => {
|
||||
if (exports.isShortcut(item.mimeType)) {
|
||||
if (isShortcut(item.mimeType)) {
|
||||
return getMimeType2(item.shortcutDetails.targetMimeType)
|
||||
}
|
||||
return getMimeType2(item.mimeType)
|
||||
|
|
@ -152,9 +152,9 @@ const getVideoWidth = (item) => item.videoMediaMetadata?.width
|
|||
const getVideoDurationMillis = (item) => item.videoMediaMetadata?.durationMillis
|
||||
|
||||
// Hopefully this name will not be used by Google
|
||||
exports.VIRTUAL_SHARED_DIR = 'shared-with-me'
|
||||
export const VIRTUAL_SHARED_DIR = 'shared-with-me'
|
||||
|
||||
exports.adaptData = (
|
||||
export const adaptData = (
|
||||
listFilesResp,
|
||||
sharedDrivesResp,
|
||||
directory,
|
||||
|
|
@ -194,8 +194,8 @@ exports.adaptData = (
|
|||
icon: 'folder',
|
||||
name: 'Shared with me',
|
||||
mimeType: 'application/vnd.google-apps.folder',
|
||||
id: exports.VIRTUAL_SHARED_DIR,
|
||||
requestPath: exports.VIRTUAL_SHARED_DIR,
|
||||
id: VIRTUAL_SHARED_DIR,
|
||||
requestPath: VIRTUAL_SHARED_DIR,
|
||||
}
|
||||
|
||||
const adaptedItems = [
|
||||
|
|
|
|||
|
|
@ -1,19 +1,18 @@
|
|||
const got = require('../../../got')
|
||||
|
||||
const { logout, refreshToken } = require('../index')
|
||||
const logger = require('../../../logger')
|
||||
const {
|
||||
VIRTUAL_SHARED_DIR,
|
||||
import got from 'got'
|
||||
import { MAX_AGE_REFRESH_TOKEN } from '../../../helpers/jwt.js'
|
||||
import { prepareStream } from '../../../helpers/utils.js'
|
||||
import logger from '../../../logger.js'
|
||||
import { ProviderAuthError } from '../../error.js'
|
||||
import Provider from '../../Provider.js'
|
||||
import { withGoogleErrorHandling } from '../../providerErrors.js'
|
||||
import { logout, refreshToken } from '../index.js'
|
||||
import {
|
||||
adaptData,
|
||||
isShortcut,
|
||||
isGsuiteFile,
|
||||
getGsuiteExportType,
|
||||
} = require('./adapter')
|
||||
const { prepareStream } = require('../../../helpers/utils')
|
||||
const { MAX_AGE_REFRESH_TOKEN } = require('../../../helpers/jwt')
|
||||
const { ProviderAuthError } = require('../../error')
|
||||
const { withGoogleErrorHandling } = require('../../providerErrors')
|
||||
const Provider = require('../../Provider')
|
||||
isGsuiteFile,
|
||||
isShortcut,
|
||||
VIRTUAL_SHARED_DIR,
|
||||
} from './adapter.js'
|
||||
|
||||
// For testing refresh token:
|
||||
// first run a download with mockAccessTokenExpiredError = true
|
||||
|
|
@ -29,8 +28,8 @@ 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 = async ({ token }) =>
|
||||
(await got).extend({
|
||||
const getClient = ({ token }) =>
|
||||
got.extend({
|
||||
prefixUrl: 'https://www.googleapis.com/drive/v3',
|
||||
headers: {
|
||||
authorization: `Bearer ${token}`,
|
||||
|
|
@ -38,7 +37,7 @@ const getClient = async ({ token }) =>
|
|||
})
|
||||
|
||||
async function getStats({ id, token }) {
|
||||
const client = await getClient({ token })
|
||||
const client = getClient({ token })
|
||||
|
||||
const getStatsInner = async (statsOfId) =>
|
||||
client
|
||||
|
|
@ -56,8 +55,8 @@ async function getStats({ id, token }) {
|
|||
return stats
|
||||
}
|
||||
|
||||
async function streamGoogleFile({ token, id: idIn }) {
|
||||
const client = await getClient({ token })
|
||||
export async function streamGoogleFile({ token, id: idIn }) {
|
||||
const client = getClient({ token })
|
||||
|
||||
const { mimeType, id, exportLinks } = await getStats({ id: idIn, token })
|
||||
|
||||
|
|
@ -76,12 +75,10 @@ async function streamGoogleFile({ token, id: idIn }) {
|
|||
// Implemented based on the answer from StackOverflow: https://stackoverflow.com/a/59168288
|
||||
const mimeTypeExportLink = exportLinks?.[mimeType2]
|
||||
if (mimeTypeExportLink) {
|
||||
const gSuiteFilesClient = (await got).extend({
|
||||
stream = got.stream.get(mimeTypeExportLink, {
|
||||
headers: {
|
||||
authorization: `Bearer ${token}`,
|
||||
},
|
||||
})
|
||||
stream = gSuiteFilesClient.stream.get(mimeTypeExportLink, {
|
||||
responseType: 'json',
|
||||
})
|
||||
} else {
|
||||
|
|
@ -104,7 +101,7 @@ async function streamGoogleFile({ token, id: idIn }) {
|
|||
/**
|
||||
* Adapter for API https://developers.google.com/drive/api/v3/
|
||||
*/
|
||||
class Drive extends Provider {
|
||||
export class Drive extends Provider {
|
||||
static get oauthProvider() {
|
||||
return 'googledrive'
|
||||
}
|
||||
|
|
@ -127,7 +124,7 @@ class Drive extends Provider {
|
|||
const isRoot = directory === 'root'
|
||||
const isVirtualSharedDirRoot = directory === VIRTUAL_SHARED_DIR
|
||||
|
||||
const client = await getClient({ token })
|
||||
const client = getClient({ token })
|
||||
|
||||
async function fetchSharedDrives(pageToken = null) {
|
||||
const shouldListSharedDrives = isRoot && !query.cursor
|
||||
|
|
@ -228,8 +225,3 @@ class Drive extends Provider {
|
|||
|
||||
Drive.prototype.logout = logout
|
||||
Drive.prototype.refreshToken = refreshToken
|
||||
|
||||
module.exports = {
|
||||
Drive,
|
||||
streamGoogleFile,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,17 +1,16 @@
|
|||
const got = require('../../got')
|
||||
|
||||
const { withGoogleErrorHandling } = require('../providerErrors')
|
||||
import got from 'got'
|
||||
import { withGoogleErrorHandling } from '../providerErrors.js'
|
||||
|
||||
/**
|
||||
* Reusable google stuff
|
||||
*/
|
||||
|
||||
const getOauthClient = async () =>
|
||||
(await got).extend({
|
||||
const getOauthClient = () =>
|
||||
got.extend({
|
||||
prefixUrl: 'https://oauth2.googleapis.com',
|
||||
})
|
||||
|
||||
async function refreshToken({
|
||||
export async function refreshToken({
|
||||
clientId,
|
||||
clientSecret,
|
||||
refreshToken: theRefreshToken,
|
||||
|
|
@ -20,7 +19,7 @@ async function refreshToken({
|
|||
'google',
|
||||
'provider.google.token.refresh.error',
|
||||
async () => {
|
||||
const { access_token: accessToken } = await (await getOauthClient())
|
||||
const { access_token: accessToken } = await getOauthClient()
|
||||
.post('token', {
|
||||
responseType: 'json',
|
||||
form: {
|
||||
|
|
@ -36,12 +35,12 @@ async function refreshToken({
|
|||
)
|
||||
}
|
||||
|
||||
async function logout({ providerUserSession: { accessToken: token } }) {
|
||||
export async function logout({ providerUserSession: { accessToken: token } }) {
|
||||
return withGoogleErrorHandling(
|
||||
'google',
|
||||
'provider.google.logout.error',
|
||||
async () => {
|
||||
await (await got).post('https://accounts.google.com/o/oauth2/revoke', {
|
||||
await got.post('https://accounts.google.com/o/oauth2/revoke', {
|
||||
searchParams: { token },
|
||||
responseType: 'json',
|
||||
})
|
||||
|
|
@ -50,8 +49,3 @@ async function logout({ providerUserSession: { accessToken: token } }) {
|
|||
},
|
||||
)
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
refreshToken,
|
||||
logout,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,21 +1,20 @@
|
|||
/**
|
||||
* @module provider
|
||||
*/
|
||||
const dropbox = require('./dropbox')
|
||||
const box = require('./box')
|
||||
const { Drive } = require('./google/drive')
|
||||
const instagram = require('./instagram/graph')
|
||||
const facebook = require('./facebook')
|
||||
const onedrive = require('./onedrive')
|
||||
const unsplash = require('./unsplash')
|
||||
const webdav = require('./webdav')
|
||||
const zoom = require('./zoom')
|
||||
const { getURLBuilder, getRedirectPath } = require('../helpers/utils')
|
||||
const logger = require('../logger')
|
||||
const { getCredentialsResolver } = require('./credentials')
|
||||
const Provider = require('./Provider')
|
||||
|
||||
const { isOAuthProvider } = Provider
|
||||
import { getRedirectPath, getURLBuilder } from '../helpers/utils.js'
|
||||
import * as logger from '../logger.js'
|
||||
import box from './box/index.js'
|
||||
import { getCredentialsResolver } from './credentials.js'
|
||||
import dropbox from './dropbox/index.js'
|
||||
import facebook from './facebook/index.js'
|
||||
import { Drive } from './google/drive/index.js'
|
||||
import instagram from './instagram/graph/index.js'
|
||||
import onedrive from './onedrive/index.js'
|
||||
import Provider, { isOAuthProvider } from './Provider.js'
|
||||
import unsplash from './unsplash/index.js'
|
||||
import webdav from './webdav/index.js'
|
||||
import zoom from './zoom/index.js'
|
||||
|
||||
/**
|
||||
*
|
||||
|
|
@ -31,7 +30,7 @@ const validOptions = (options) => {
|
|||
*
|
||||
* @param {Record<string, typeof Provider>} providers
|
||||
*/
|
||||
module.exports.getProviderMiddleware = (providers, grantConfig) => {
|
||||
export function getProviderMiddleware(providers, grantConfig) {
|
||||
/**
|
||||
*
|
||||
* @param {object} req
|
||||
|
|
@ -81,7 +80,7 @@ module.exports.getProviderMiddleware = (providers, grantConfig) => {
|
|||
/**
|
||||
* @returns {Record<string, typeof Provider>}
|
||||
*/
|
||||
module.exports.getDefaultProviders = () => {
|
||||
export function getDefaultProviders() {
|
||||
const providers = {
|
||||
dropbox,
|
||||
box,
|
||||
|
|
@ -105,11 +104,7 @@ module.exports.getDefaultProviders = () => {
|
|||
* @param {Record<string, typeof Provider>} providers
|
||||
* @param {object} grantConfig
|
||||
*/
|
||||
module.exports.addCustomProviders = (
|
||||
customProviders,
|
||||
providers,
|
||||
grantConfig,
|
||||
) => {
|
||||
export function addCustomProviders(customProviders, providers, grantConfig) {
|
||||
Object.keys(customProviders).forEach((providerName) => {
|
||||
const customProvider = customProviders[providerName]
|
||||
|
||||
|
|
@ -135,11 +130,11 @@ module.exports.addCustomProviders = (
|
|||
* @param {object} grantConfig
|
||||
* @param {(a: string) => string} getOauthProvider
|
||||
*/
|
||||
module.exports.addProviderOptions = (
|
||||
export function addProviderOptions(
|
||||
companionOptions,
|
||||
grantConfig,
|
||||
getOauthProvider,
|
||||
) => {
|
||||
) {
|
||||
const { server, providerOptions } = companionOptions
|
||||
if (!validOptions({ server })) {
|
||||
logger.warn(
|
||||
|
|
@ -173,7 +168,7 @@ module.exports.addProviderOptions = (
|
|||
]
|
||||
}
|
||||
|
||||
const provider = exports.getDefaultProviders()[providerName]
|
||||
const provider = getDefaultProviders()[providerName]
|
||||
Object.assign(grantConfig[oauthProvider], provider.getExtraGrantConfig())
|
||||
|
||||
// override grant.js redirect uri with companion's custom redirect url
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
const querystring = require('node:querystring')
|
||||
import querystring from 'node:querystring'
|
||||
|
||||
const MEDIA_TYPES = Object.freeze({
|
||||
video: 'VIDEO',
|
||||
|
|
@ -55,7 +55,7 @@ const getNextPagePath = (data, currentQuery, currentPath) => {
|
|||
return `${currentPath || ''}?${querystring.stringify(query)}`
|
||||
}
|
||||
|
||||
module.exports = (res, username, directory, currentQuery) => {
|
||||
const adaptData = (res, username, directory, currentQuery) => {
|
||||
const data = { username, items: [] }
|
||||
const items = getItemSubList(res)
|
||||
items.forEach((item, i) => {
|
||||
|
|
@ -75,3 +75,5 @@ module.exports = (res, username, directory, currentQuery) => {
|
|||
data.nextPagePath = getNextPagePath(res, currentQuery, directory)
|
||||
return data
|
||||
}
|
||||
|
||||
export default adaptData
|
||||
|
|
|
|||
|
|
@ -1,13 +1,12 @@
|
|||
const Provider = require('../../Provider')
|
||||
const logger = require('../../../logger')
|
||||
const adaptData = require('./adapter')
|
||||
const { withProviderErrorHandling } = require('../../providerErrors')
|
||||
const { prepareStream } = require('../../../helpers/utils')
|
||||
import got from 'got'
|
||||
import { prepareStream } from '../../../helpers/utils.js'
|
||||
import logger from '../../../logger.js'
|
||||
import Provider from '../../Provider.js'
|
||||
import { withProviderErrorHandling } from '../../providerErrors.js'
|
||||
import adaptData from './adapter.js'
|
||||
|
||||
const got = require('../../../got')
|
||||
|
||||
const getClient = async ({ token }) =>
|
||||
(await got).extend({
|
||||
const getClient = ({ token }) =>
|
||||
got.extend({
|
||||
prefixUrl: 'https://graph.instagram.com',
|
||||
headers: {
|
||||
authorization: `Bearer ${token}`,
|
||||
|
|
@ -15,7 +14,7 @@ const getClient = async ({ token }) =>
|
|||
})
|
||||
|
||||
async function getMediaUrl({ token, id }) {
|
||||
const body = await (await getClient({ token }))
|
||||
const body = await getClient({ token })
|
||||
.get(String(id), {
|
||||
searchParams: { fields: 'media_url' },
|
||||
responseType: 'json',
|
||||
|
|
@ -27,7 +26,7 @@ async function getMediaUrl({ token, id }) {
|
|||
/**
|
||||
* Adapter for API https://developers.facebook.com/docs/instagram-api/overview
|
||||
*/
|
||||
class Instagram extends Provider {
|
||||
export default class Instagram extends Provider {
|
||||
// for "grant"
|
||||
static getExtraGrantConfig() {
|
||||
return {
|
||||
|
|
@ -55,7 +54,7 @@ class Instagram extends Provider {
|
|||
|
||||
if (query.cursor) qs.after = query.cursor
|
||||
|
||||
const client = await getClient({ token })
|
||||
const client = getClient({ token })
|
||||
|
||||
const [{ username }, list] = await Promise.all([
|
||||
client
|
||||
|
|
@ -78,7 +77,7 @@ class Instagram extends Provider {
|
|||
'provider.instagram.download.error',
|
||||
async () => {
|
||||
const url = await getMediaUrl({ token, id })
|
||||
const stream = (await got).stream.get(url, { responseType: 'json' })
|
||||
const stream = got.stream.get(url, { responseType: 'json' })
|
||||
const { size } = await prepareStream(stream)
|
||||
return { stream, size }
|
||||
},
|
||||
|
|
@ -113,5 +112,3 @@ class Instagram extends Provider {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Instagram
|
||||
|
|
|
|||
|
|
@ -61,7 +61,7 @@ const getNextPagePath = ({ res, query: currentQuery, directory }) => {
|
|||
return `${directory ?? ''}?${new URLSearchParams(query).toString()}`
|
||||
}
|
||||
|
||||
module.exports = (res, username, query, directory) => {
|
||||
const adaptData = (res, username, query, directory) => {
|
||||
const data = { username, items: [] }
|
||||
const items = getItemSubList(res)
|
||||
items.forEach((item) => {
|
||||
|
|
@ -82,3 +82,5 @@ module.exports = (res, username, query, directory) => {
|
|||
|
||||
return data
|
||||
}
|
||||
|
||||
export default adaptData
|
||||
|
|
|
|||
|
|
@ -1,21 +1,20 @@
|
|||
const Provider = require('../Provider')
|
||||
const logger = require('../../logger')
|
||||
const adaptData = require('./adapter')
|
||||
const { withProviderErrorHandling } = require('../providerErrors')
|
||||
const { prepareStream } = require('../../helpers/utils')
|
||||
import got from 'got'
|
||||
import { prepareStream } from '../../helpers/utils.js'
|
||||
import logger from '../../logger.js'
|
||||
import Provider from '../Provider.js'
|
||||
import { withProviderErrorHandling } from '../providerErrors.js'
|
||||
import adaptData from './adapter.js'
|
||||
|
||||
const got = require('../../got')
|
||||
|
||||
const getClient = async ({ token }) =>
|
||||
(await got).extend({
|
||||
const getClient = ({ token }) =>
|
||||
got.extend({
|
||||
prefixUrl: 'https://graph.microsoft.com/v1.0',
|
||||
headers: {
|
||||
authorization: `Bearer ${token}`,
|
||||
},
|
||||
})
|
||||
|
||||
const getOauthClient = async () =>
|
||||
(await got).extend({
|
||||
const getOauthClient = () =>
|
||||
got.extend({
|
||||
prefixUrl: 'https://login.live.com',
|
||||
})
|
||||
|
||||
|
|
@ -25,7 +24,7 @@ const getRootPath = (query) =>
|
|||
/**
|
||||
* Adapter for API https://docs.microsoft.com/en-us/onedrive/developer/rest-api/
|
||||
*/
|
||||
class OneDrive extends Provider {
|
||||
export default class OneDrive extends Provider {
|
||||
static get oauthProvider() {
|
||||
return 'microsoft'
|
||||
}
|
||||
|
|
@ -54,7 +53,7 @@ class OneDrive extends Provider {
|
|||
qs.$skiptoken = query.cursor
|
||||
}
|
||||
|
||||
const client = await getClient({ token })
|
||||
const client = getClient({ token })
|
||||
|
||||
const [{ mail, userPrincipalName }, list] = await Promise.all([
|
||||
client.get('me', { responseType: 'json' }).json(),
|
||||
|
|
@ -74,7 +73,7 @@ class OneDrive extends Provider {
|
|||
return this.#withErrorHandling(
|
||||
'provider.onedrive.download.error',
|
||||
async () => {
|
||||
const stream = (await getClient({ token })).stream.get(
|
||||
const stream = getClient({ token }).stream.get(
|
||||
`${getRootPath(query)}/items/${id}/content`,
|
||||
{ responseType: 'json' },
|
||||
)
|
||||
|
|
@ -95,7 +94,7 @@ class OneDrive extends Provider {
|
|||
|
||||
async size({ id, query, providerUserSession: { accessToken: token } }) {
|
||||
return this.#withErrorHandling('provider.onedrive.size.error', async () => {
|
||||
const { size } = await (await getClient({ token }))
|
||||
const { size } = await getClient({ token })
|
||||
.get(`${getRootPath(query)}/items/${id}`, { responseType: 'json' })
|
||||
.json()
|
||||
return size
|
||||
|
|
@ -114,7 +113,7 @@ class OneDrive extends Provider {
|
|||
return this.#withErrorHandling(
|
||||
'provider.onedrive.token.refresh.error',
|
||||
async () => {
|
||||
const { access_token: accessToken } = await (await getOauthClient())
|
||||
const { access_token: accessToken } = await getOauthClient()
|
||||
.post('oauth20_token.srf', {
|
||||
responseType: 'json',
|
||||
form: {
|
||||
|
|
@ -147,5 +146,3 @@ class OneDrive extends Provider {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = OneDrive
|
||||
|
|
|
|||
|
|
@ -1,10 +1,12 @@
|
|||
const logger = require('../logger')
|
||||
const {
|
||||
import * as logger from '../logger.js'
|
||||
import {
|
||||
ProviderApiError,
|
||||
ProviderUserError,
|
||||
ProviderAuthError,
|
||||
ProviderUserError,
|
||||
parseHttpError,
|
||||
} = require('./error')
|
||||
} from './error.js'
|
||||
|
||||
export { parseHttpError }
|
||||
|
||||
/**
|
||||
*
|
||||
|
|
@ -18,7 +20,7 @@ const {
|
|||
* }} param0
|
||||
* @returns
|
||||
*/
|
||||
async function withProviderErrorHandling({
|
||||
export async function withProviderErrorHandling({
|
||||
fn,
|
||||
tag,
|
||||
providerName,
|
||||
|
|
@ -71,7 +73,7 @@ async function withProviderErrorHandling({
|
|||
}
|
||||
}
|
||||
|
||||
async function withGoogleErrorHandling(providerName, tag, fn) {
|
||||
export async function withGoogleErrorHandling(providerName, tag, fn) {
|
||||
return withProviderErrorHandling({
|
||||
fn,
|
||||
tag,
|
||||
|
|
@ -82,9 +84,3 @@ async function withGoogleErrorHandling(providerName, tag, fn) {
|
|||
getJsonErrorMessage: (body) => body?.error?.message,
|
||||
})
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
withProviderErrorHandling,
|
||||
withGoogleErrorHandling,
|
||||
parseHttpError,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
const querystring = require('node:querystring')
|
||||
import querystring from 'node:querystring'
|
||||
|
||||
const isFolder = (item) => {
|
||||
return false
|
||||
|
|
@ -55,7 +55,7 @@ const getAuthor = (item) => {
|
|||
return { name: item.user.name, url: item.user.links.html }
|
||||
}
|
||||
|
||||
module.exports = (body, currentQuery) => {
|
||||
const adaptData = (body, currentQuery) => {
|
||||
const { total_pages: pagesCount } = body
|
||||
const { cursor, q } = currentQuery
|
||||
const currentPage = Number(cursor || 1)
|
||||
|
|
@ -80,3 +80,5 @@ module.exports = (body, currentQuery) => {
|
|||
nextPageQuery: hasNextPage ? getNextPageQuery(currentQuery) : null,
|
||||
}
|
||||
}
|
||||
|
||||
export default adaptData
|
||||
|
|
|
|||
|
|
@ -1,15 +1,14 @@
|
|||
const Provider = require('../Provider')
|
||||
const adaptData = require('./adapter')
|
||||
const { withProviderErrorHandling } = require('../providerErrors')
|
||||
const { prepareStream } = require('../../helpers/utils')
|
||||
const { ProviderApiError } = require('../error')
|
||||
|
||||
const got = require('../../got')
|
||||
import got from 'got'
|
||||
import { prepareStream } from '../../helpers/utils.js'
|
||||
import { ProviderApiError } from '../error.js'
|
||||
import Provider from '../Provider.js'
|
||||
import { withProviderErrorHandling } from '../providerErrors.js'
|
||||
import adaptData from './adapter.js'
|
||||
|
||||
const BASE_URL = 'https://api.unsplash.com'
|
||||
|
||||
const getClient = async ({ token }) =>
|
||||
(await got).extend({
|
||||
const getClient = ({ token }) =>
|
||||
got.extend({
|
||||
prefixUrl: BASE_URL,
|
||||
headers: {
|
||||
authorization: `Client-ID ${token}`,
|
||||
|
|
@ -22,7 +21,7 @@ const getPhotoMeta = async (client, id) =>
|
|||
/**
|
||||
* Adapter for API https://api.unsplash.com
|
||||
*/
|
||||
class Unsplash extends Provider {
|
||||
export default class Unsplash extends Provider {
|
||||
async list({
|
||||
providerUserSession: { accessToken: token },
|
||||
query = { cursor: null, q: null },
|
||||
|
|
@ -35,7 +34,7 @@ class Unsplash extends Provider {
|
|||
const qs = { per_page: 40, query: query.q }
|
||||
if (query.cursor) qs.page = query.cursor
|
||||
|
||||
const response = await (await getClient({ token }))
|
||||
const response = await getClient({ token })
|
||||
.get('search/photos', { searchParams: qs, responseType: 'json' })
|
||||
.json()
|
||||
return adaptData(response, query)
|
||||
|
|
@ -46,13 +45,13 @@ class Unsplash extends Provider {
|
|||
return this.#withErrorHandling(
|
||||
'provider.unsplash.download.error',
|
||||
async () => {
|
||||
const client = await getClient({ token })
|
||||
const client = getClient({ token })
|
||||
|
||||
const {
|
||||
links: { download: url, download_location: attributionUrl },
|
||||
} = await getPhotoMeta(client, id)
|
||||
|
||||
const stream = (await got).stream.get(url, { responseType: 'json' })
|
||||
const stream = got.stream.get(url, { responseType: 'json' })
|
||||
const { size } = await prepareStream(stream)
|
||||
|
||||
// To attribute the author of the image, we call the `download_location`
|
||||
|
|
@ -79,5 +78,3 @@ class Unsplash extends Provider {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Unsplash
|
||||
|
|
|
|||
|
|
@ -1,15 +1,19 @@
|
|||
const Provider = require('../Provider')
|
||||
const { getProtectedHttpAgent, validateURL } = require('../../helpers/request')
|
||||
const { ProviderApiError, ProviderAuthError } = require('../error')
|
||||
const { ProviderUserError } = require('../error')
|
||||
const logger = require('../../logger')
|
||||
import { AuthType, createClient } from 'webdav'
|
||||
import { getProtectedHttpAgent, validateURL } from '../../helpers/request.js'
|
||||
import logger from '../../logger.js'
|
||||
import {
|
||||
ProviderApiError,
|
||||
ProviderAuthError,
|
||||
ProviderUserError,
|
||||
} from '../error.js'
|
||||
import Provider from '../Provider.js'
|
||||
|
||||
const defaultDirectory = '/'
|
||||
|
||||
/**
|
||||
* Adapter for WebDAV servers that support simple auth (non-OAuth).
|
||||
*/
|
||||
class WebdavProvider extends Provider {
|
||||
export default class WebdavProvider extends Provider {
|
||||
static get hasSimpleAuth() {
|
||||
return true
|
||||
}
|
||||
|
|
@ -25,11 +29,6 @@ class WebdavProvider extends Provider {
|
|||
throw new Error('invalid public link url')
|
||||
}
|
||||
|
||||
// dynamic import because Companion currently uses CommonJS and webdav is shipped as ESM
|
||||
// todo implement as regular require as soon as Node 20.17 or 22 is required
|
||||
// or as regular import when Companion is ported to ESM
|
||||
const { AuthType } = await import('webdav')
|
||||
|
||||
// Is this an ownCloud or Nextcloud public link URL? e.g. https://example.com/s/kFy9Lek5sm928xP
|
||||
// they have specific urls that we can identify
|
||||
// todo not sure if this is the right way to support nextcloud and other webdavs
|
||||
|
|
@ -84,10 +83,6 @@ class WebdavProvider extends Provider {
|
|||
allowLocalIPs: !allowLocalUrls,
|
||||
})
|
||||
|
||||
// dynamic import because Companion currently uses CommonJS and webdav is shipped as ESM
|
||||
// todo implement as regular require as soon as Node 20.17 or 22 is required
|
||||
// or as regular import when Companion is ported to ESM
|
||||
const { createClient } = await import('webdav')
|
||||
return createClient(url, {
|
||||
...options,
|
||||
[`${protocol}Agent`]: new HttpAgentClass(),
|
||||
|
|
@ -174,5 +169,3 @@ class WebdavProvider extends Provider {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = WebdavProvider
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
const moment = require('moment-timezone')
|
||||
import moment from 'moment-timezone'
|
||||
|
||||
const MIMETYPES = {
|
||||
MP4: 'video/mp4',
|
||||
|
|
@ -109,7 +109,7 @@ const getItemTopic = (item) => {
|
|||
return item.topic
|
||||
}
|
||||
|
||||
exports.adaptData = (userResponse, results) => {
|
||||
const adaptData = (userResponse, results) => {
|
||||
if (!results) {
|
||||
return { items: [] }
|
||||
}
|
||||
|
|
@ -162,3 +162,5 @@ exports.adaptData = (userResponse, results) => {
|
|||
})
|
||||
return data
|
||||
}
|
||||
|
||||
export default adaptData
|
||||
|
|
|
|||
|
|
@ -1,20 +1,17 @@
|
|||
const moment = require('moment-timezone')
|
||||
|
||||
const Provider = require('../Provider')
|
||||
const { adaptData } = require('./adapter')
|
||||
const { withProviderErrorHandling } = require('../providerErrors')
|
||||
const { prepareStream, getBasicAuthHeader } = require('../../helpers/utils')
|
||||
|
||||
const got = require('../../got')
|
||||
|
||||
const pMap = import('p-map')
|
||||
import got from 'got'
|
||||
import moment from 'moment-timezone'
|
||||
import pMap from 'p-map'
|
||||
import { getBasicAuthHeader, prepareStream } from '../../helpers/utils.js'
|
||||
import Provider from '../Provider.js'
|
||||
import { withProviderErrorHandling } from '../providerErrors.js'
|
||||
import adaptData from './adapter.js'
|
||||
|
||||
const BASE_URL = 'https://zoom.us/v2'
|
||||
const PAGE_SIZE = 300
|
||||
const DEAUTH_EVENT_NAME = 'app_deauthorized'
|
||||
|
||||
const getClient = async ({ token }) =>
|
||||
(await got).extend({
|
||||
const getClient = ({ token }) =>
|
||||
got.extend({
|
||||
prefixUrl: BASE_URL,
|
||||
headers: {
|
||||
authorization: `Bearer ${token}`,
|
||||
|
|
@ -38,7 +35,7 @@ async function findFile({ client, meetingId, fileId, recordingStart }) {
|
|||
/**
|
||||
* Adapter for API https://marketplace.zoom.us/docs/api-reference/zoom-api
|
||||
*/
|
||||
class Zoom extends Provider {
|
||||
export default class Zoom extends Provider {
|
||||
static get oauthProvider() {
|
||||
return 'zoom'
|
||||
}
|
||||
|
|
@ -52,7 +49,7 @@ class Zoom extends Provider {
|
|||
const meetingId = options.directory || ''
|
||||
const requestedYear = query.year ? parseInt(query.year, 10) : null
|
||||
|
||||
const client = await getClient({ token })
|
||||
const client = getClient({ token })
|
||||
const user = await client.get('users/me', { responseType: 'json' }).json()
|
||||
const { timezone } = user
|
||||
const userTz = timezone || 'UTC'
|
||||
|
|
@ -74,9 +71,7 @@ class Zoom extends Provider {
|
|||
|
||||
// Run each month in parallel:
|
||||
const allMeetingsInYear = (
|
||||
await (
|
||||
await pMap
|
||||
).default(
|
||||
await pMap(
|
||||
monthsToCheck,
|
||||
async (month) => {
|
||||
const startDate = moment
|
||||
|
|
@ -153,7 +148,7 @@ class Zoom extends Provider {
|
|||
// cc files don't have an ID or size
|
||||
const { recordingStart, recordingId: fileId } = query
|
||||
|
||||
const client = await getClient({ token })
|
||||
const client = getClient({ token })
|
||||
|
||||
const foundFile = await findFile({
|
||||
client,
|
||||
|
|
@ -179,7 +174,7 @@ class Zoom extends Provider {
|
|||
query,
|
||||
}) {
|
||||
return this.#withErrorHandling('provider.zoom.size.error', async () => {
|
||||
const client = await getClient({ token })
|
||||
const client = getClient({ token })
|
||||
const { recordingStart, recordingId: fileId } = query
|
||||
|
||||
const foundFile = await findFile({
|
||||
|
|
@ -197,7 +192,7 @@ class Zoom extends Provider {
|
|||
return this.#withErrorHandling('provider.zoom.logout.error', async () => {
|
||||
const { key, secret } = await companion.getProviderCredentials()
|
||||
|
||||
const { status } = await (await got)
|
||||
const { status } = await got
|
||||
.post('https://zoom.us/oauth/revoke', {
|
||||
searchParams: { token },
|
||||
headers: { Authorization: getBasicAuthHeader(key, secret) },
|
||||
|
|
@ -223,7 +218,7 @@ class Zoom extends Provider {
|
|||
return { data: {}, status: 400 }
|
||||
}
|
||||
|
||||
await (await got).post('https://api.zoom.us/oauth/data/compliance', {
|
||||
await got.post('https://api.zoom.us/oauth/data/compliance', {
|
||||
headers: { Authorization: getBasicAuthHeader(key, secret) },
|
||||
json: {
|
||||
client_id: key,
|
||||
|
|
@ -254,5 +249,3 @@ class Zoom extends Provider {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Zoom
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
const Redis = require('ioredis').default
|
||||
|
||||
const logger = require('./logger')
|
||||
import { Redis } from 'ioredis'
|
||||
import * as logger from './logger.js'
|
||||
|
||||
/** @type {import('ioredis').Redis} */
|
||||
let redisClient
|
||||
|
|
@ -27,9 +26,9 @@ function createClient(redisUrl, redisOptions) {
|
|||
return redisClient
|
||||
}
|
||||
|
||||
module.exports.client = (
|
||||
export function client(
|
||||
{ redisUrl, redisOptions } = { redisUrl: undefined, redisOptions: undefined },
|
||||
) => {
|
||||
) {
|
||||
if (!redisUrl && !redisOptions) {
|
||||
return redisClient
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
const { S3Client } = require('@aws-sdk/client-s3')
|
||||
import { S3Client } from '@aws-sdk/client-s3'
|
||||
|
||||
/**
|
||||
* instantiates the aws-sdk s3 client that will be used for s3 uploads.
|
||||
|
|
@ -6,7 +6,10 @@ const { S3Client } = require('@aws-sdk/client-s3')
|
|||
* @param {object} companionOptions the companion options object
|
||||
* @param {boolean} createPresignedPostMode whether this s3 client is for createPresignedPost
|
||||
*/
|
||||
module.exports = (companionOptions, createPresignedPostMode = false) => {
|
||||
export default function s3Client(
|
||||
companionOptions,
|
||||
createPresignedPostMode = false,
|
||||
) {
|
||||
let s3Client = null
|
||||
if (companionOptions.s3) {
|
||||
const { s3 } = companionOptions
|
||||
|
|
|
|||
|
|
@ -1,17 +1,17 @@
|
|||
const SocketServer = require('ws').WebSocketServer
|
||||
const { jsonStringify } = require('./helpers/utils')
|
||||
const emitter = require('./emitter')
|
||||
const redis = require('./redis')
|
||||
const logger = require('./logger')
|
||||
const { STORAGE_PREFIX, shortenToken } = require('./Uploader')
|
||||
import { WebSocketServer } from 'ws'
|
||||
import emitter from './emitter/index.js'
|
||||
import { jsonStringify } from './helpers/utils.js'
|
||||
import * as logger from './logger.js'
|
||||
import * as redis from './redis.js'
|
||||
import Uploader from './Uploader.js'
|
||||
|
||||
/**
|
||||
* the socket is used to send progress events during an upload
|
||||
*
|
||||
* @param {import('http').Server | import('https').Server} server
|
||||
*/
|
||||
module.exports = (server) => {
|
||||
const wss = new SocketServer({ server })
|
||||
export default function setupSocket(server) {
|
||||
const wss = new WebSocketServer({ server })
|
||||
const redisClient = redis.client()
|
||||
|
||||
// A new connection is usually created when an upload begins,
|
||||
|
|
@ -30,7 +30,8 @@ module.exports = (server) => {
|
|||
*/
|
||||
function send(data) {
|
||||
ws.send(jsonStringify(data), (err) => {
|
||||
if (err) logger.error(err, 'socket.redis.error', shortenToken(token))
|
||||
if (err)
|
||||
logger.error(err, 'socket.redis.error', Uploader.shortenToken(token))
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -38,7 +39,7 @@ module.exports = (server) => {
|
|||
// if we have any already stored state on the upload.
|
||||
if (redisClient) {
|
||||
redisClient
|
||||
.get(`${STORAGE_PREFIX}:${token}`)
|
||||
.get(`${Uploader.STORAGE_PREFIX}:${token}`)
|
||||
.then((data) => {
|
||||
if (data) {
|
||||
const dataObj = JSON.parse(data.toString())
|
||||
|
|
@ -46,7 +47,7 @@ module.exports = (server) => {
|
|||
}
|
||||
})
|
||||
.catch((err) =>
|
||||
logger.error(err, 'socket.redis.error', shortenToken(token)),
|
||||
logger.error(err, 'socket.redis.error', Uploader.shortenToken(token)),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -64,10 +65,10 @@ module.exports = (server) => {
|
|||
logger.error(
|
||||
'WebSocket message too large',
|
||||
'websocket.error',
|
||||
shortenToken(token),
|
||||
Uploader.shortenToken(token),
|
||||
)
|
||||
} else {
|
||||
logger.error(err, 'websocket.error', shortenToken(token))
|
||||
logger.error(err, 'websocket.error', Uploader.shortenToken(token))
|
||||
}
|
||||
})
|
||||
|
||||
|
|
|
|||
|
|
@ -1,12 +1,10 @@
|
|||
const fs = require('node:fs')
|
||||
const merge = require('lodash/merge')
|
||||
const stripIndent = require('common-tags/lib/stripIndent')
|
||||
const crypto = require('node:crypto')
|
||||
|
||||
const utils = require('../server/helpers/utils')
|
||||
const logger = require('../server/logger')
|
||||
// @ts-ignore
|
||||
const { version } = require('../../package.json')
|
||||
import crypto from 'node:crypto'
|
||||
import fs from 'node:fs'
|
||||
import { stripIndent } from 'common-tags'
|
||||
import merge from 'lodash/merge.js'
|
||||
import packageJson from '../../package.json' with { type: 'json' }
|
||||
import * as utils from '../server/helpers/utils.js'
|
||||
import logger from '../server/logger.js'
|
||||
|
||||
/**
|
||||
* Tries to read the secret from a file if the according environment variable is set.
|
||||
|
|
@ -28,7 +26,7 @@ const getSecret = (baseEnvVar) => {
|
|||
*
|
||||
* @returns {string}
|
||||
*/
|
||||
exports.generateSecret = (secretName) => {
|
||||
export const generateSecret = (secretName) => {
|
||||
logger.warn(
|
||||
`auto-generating server ${secretName} because none was specified`,
|
||||
'startup.secret',
|
||||
|
|
@ -261,11 +259,11 @@ const getConfigFromFile = () => {
|
|||
*
|
||||
* @returns {object}
|
||||
*/
|
||||
exports.getCompanionOptions = (options = {}) => {
|
||||
export const getCompanionOptions = (options = {}) => {
|
||||
return merge({}, getConfigFromEnv(), getConfigFromFile(), options)
|
||||
}
|
||||
|
||||
exports.buildHelpfulStartupMessage = (companionOptions) => {
|
||||
export const buildHelpfulStartupMessage = (companionOptions) => {
|
||||
const buildURL = utils.getURLBuilder(companionOptions)
|
||||
const callbackURLs = []
|
||||
Object.keys(companionOptions.providerOptions).forEach((providerName) => {
|
||||
|
|
@ -273,7 +271,7 @@ exports.buildHelpfulStartupMessage = (companionOptions) => {
|
|||
})
|
||||
|
||||
return stripIndent`
|
||||
Welcome to Companion v${version}
|
||||
Welcome to Companion v${packageJson.version}
|
||||
===================================
|
||||
|
||||
Congratulations on setting up Companion! Thanks for joining our cause, you have taken
|
||||
|
|
|
|||
|
|
@ -1,27 +1,26 @@
|
|||
const express = require('express')
|
||||
const qs = require('node:querystring')
|
||||
const { randomUUID } = require('node:crypto')
|
||||
const helmet = require('helmet')
|
||||
const morgan = require('morgan')
|
||||
const { URL } = require('node:url')
|
||||
const session = require('express-session')
|
||||
const RedisStore = require('connect-redis').default
|
||||
|
||||
const logger = require('../server/logger')
|
||||
const redis = require('../server/redis')
|
||||
const companion = require('../companion')
|
||||
const {
|
||||
getCompanionOptions,
|
||||
generateSecret,
|
||||
import { randomUUID } from 'node:crypto'
|
||||
import qs from 'node:querystring'
|
||||
import { URL } from 'node:url'
|
||||
import RedisStore from 'connect-redis'
|
||||
import express from 'express'
|
||||
import session from 'express-session'
|
||||
import helmet from 'helmet'
|
||||
import morgan from 'morgan'
|
||||
import * as companion from '../companion.js'
|
||||
import logger from '../server/logger.js'
|
||||
import * as redis from '../server/redis.js'
|
||||
import {
|
||||
buildHelpfulStartupMessage,
|
||||
} = require('./helper')
|
||||
generateSecret,
|
||||
getCompanionOptions,
|
||||
} from './helper.js'
|
||||
|
||||
/**
|
||||
* Configures an Express app for running Companion standalone
|
||||
*
|
||||
* @returns {object}
|
||||
*/
|
||||
module.exports = function server(inputCompanionOptions) {
|
||||
export default function server(inputCompanionOptions) {
|
||||
const companionOptions = getCompanionOptions(inputCompanionOptions)
|
||||
|
||||
companion.setLoggerProcessName(companionOptions)
|
||||
|
|
|
|||
|
|
@ -1,9 +1,8 @@
|
|||
#!/usr/bin/env node
|
||||
const companion = require('../companion')
|
||||
// @ts-ignore
|
||||
const { version } = require('../../package.json')
|
||||
const standalone = require('.')
|
||||
const logger = require('../server/logger')
|
||||
import packageJson from '../../package.json' with { type: 'json' }
|
||||
import * as companion from '../companion.js'
|
||||
import logger from '../server/logger.js'
|
||||
import standalone from './index.js'
|
||||
|
||||
const port = process.env.COMPANION_PORT || process.env.PORT || 3020
|
||||
|
||||
|
|
@ -11,5 +10,5 @@ const { app } = standalone()
|
|||
|
||||
companion.socket(app.listen(port))
|
||||
|
||||
logger.info(`Welcome to Companion! v${version}`)
|
||||
logger.info(`Welcome to Companion! v${packageJson.version}`)
|
||||
logger.info(`Listening on http://localhost:${port}`)
|
||||
|
|
|
|||
|
|
@ -1,27 +1,17 @@
|
|||
const mockOauthState = require('../mockoauthstate')()
|
||||
import request from 'supertest'
|
||||
import { describe, expect, test, vi } from 'vitest'
|
||||
import * as tokenService from '../src/server/helpers/jwt.js'
|
||||
import mockOauthState from './mockoauthstate.js'
|
||||
import { getServer, grantToken } from './mockserver.js'
|
||||
|
||||
const request = require('supertest')
|
||||
const tokenService = require('../../src/server/helpers/jwt')
|
||||
const { getServer, grantToken } = require('../mockserver')
|
||||
vi.mock('express-prom-bundle')
|
||||
mockOauthState()
|
||||
|
||||
jest.mock('../../src/server/helpers/oauth-state', () => ({
|
||||
...jest.requireActual('../../src/server/helpers/oauth-state'),
|
||||
...mockOauthState,
|
||||
}))
|
||||
|
||||
const authServer = getServer()
|
||||
const authData = {
|
||||
dropbox: { accessToken: 'token value' },
|
||||
drive: { accessToken: 'token value' },
|
||||
}
|
||||
const token = tokenService.generateEncryptedAuthToken(
|
||||
authData,
|
||||
process.env.COMPANION_SECRET,
|
||||
)
|
||||
const secret = 'secret'
|
||||
|
||||
describe('test authentication callback', () => {
|
||||
test('authentication callback redirects to send-token url', () => {
|
||||
return request(authServer)
|
||||
test('authentication callback redirects to send-token url', async () => {
|
||||
return request(await getServer())
|
||||
.get('/drive/callback')
|
||||
.expect(302)
|
||||
.expect((res) => {
|
||||
|
|
@ -31,9 +21,8 @@ describe('test authentication callback', () => {
|
|||
})
|
||||
})
|
||||
|
||||
test('authentication callback sets cookie', () => {
|
||||
console.log(process.env.COMPANION_SECRET)
|
||||
return request(authServer)
|
||||
test('authentication callback sets cookie', async () => {
|
||||
return request(await getServer())
|
||||
.get('/dropbox/callback')
|
||||
.expect(302)
|
||||
.expect((res) => {
|
||||
|
|
@ -47,17 +36,23 @@ describe('test authentication callback', () => {
|
|||
)
|
||||
const payload = tokenService.verifyEncryptedAuthToken(
|
||||
authToken,
|
||||
process.env.COMPANION_SECRET,
|
||||
secret,
|
||||
'dropbox',
|
||||
)
|
||||
expect(payload).toEqual({ dropbox: { accessToken: grantToken } })
|
||||
})
|
||||
})
|
||||
|
||||
test('the token gets sent via html', () => {
|
||||
test('the token gets sent via html', async () => {
|
||||
const authData = {
|
||||
dropbox: { accessToken: 'token value' },
|
||||
drive: { accessToken: 'token value' },
|
||||
}
|
||||
const token = tokenService.generateEncryptedAuthToken(authData, secret)
|
||||
|
||||
// see mock ../../src/server/helpers/oauth-state above for state values
|
||||
return request(authServer)
|
||||
.get(`/dropbox/send-token?uppyAuthToken=${token}`)
|
||||
return request(await getServer())
|
||||
.get(`/dropbox/send-token?uppyAuthToken=${encodeURIComponent(token)}`)
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
expect(res.text).toMatch(`var data = {"token":"${token}"};`)
|
||||
|
|
@ -1,46 +1,40 @@
|
|||
const nock = require('nock')
|
||||
const request = require('supertest')
|
||||
import nock from 'nock'
|
||||
import request from 'supertest'
|
||||
import { afterAll, describe, expect, it, test, vi } from 'vitest'
|
||||
import packageJson from '../package.json' with { type: 'json' }
|
||||
import * as tokenService from '../src/server/helpers/jwt.js'
|
||||
import * as defaults from './fixtures/constants.js'
|
||||
import { nockGoogleDownloadFile } from './fixtures/drive.js'
|
||||
import mockOauthState from './mockoauthstate.js'
|
||||
import { getServer } from './mockserver.js'
|
||||
|
||||
const mockOauthState = require('../mockoauthstate')
|
||||
const { version } = require('../../package.json')
|
||||
const { nockGoogleDownloadFile } = require('../fixtures/drive')
|
||||
const defaults = require('../fixtures/constants')
|
||||
|
||||
jest.mock('tus-js-client')
|
||||
jest.mock('../../src/server/helpers/oauth-state', () => ({
|
||||
...jest.requireActual('../../src/server/helpers/oauth-state'),
|
||||
...mockOauthState(),
|
||||
}))
|
||||
vi.mock('express-prom-bundle')
|
||||
vi.mock('tus-js-client')
|
||||
mockOauthState()
|
||||
|
||||
const fakeLocalhost = 'localhost.com'
|
||||
|
||||
jest.mock('node:dns', () => {
|
||||
const actual = jest.requireActual('node:dns')
|
||||
return {
|
||||
...actual,
|
||||
vi.mock('node:dns', () => ({
|
||||
default: {
|
||||
lookup: (hostname, options, callback) => {
|
||||
if (fakeLocalhost === hostname || hostname === 'localhost') {
|
||||
return callback(null, '127.0.0.1', 4)
|
||||
}
|
||||
return callback(new Error(`Unexpected call to hostname ${hostname}`))
|
||||
},
|
||||
}
|
||||
})
|
||||
},
|
||||
}))
|
||||
|
||||
const tokenService = require('../../src/server/helpers/jwt')
|
||||
const { getServer } = require('../mockserver')
|
||||
const getServerWithEnv = async () =>
|
||||
getServer({ COMPANION_CLIENT_SOCKET_CONNECT_TIMEOUT: '0' })
|
||||
|
||||
// todo don't share server between tests. rewrite to not use env variables
|
||||
const authServer = getServer({ COMPANION_CLIENT_SOCKET_CONNECT_TIMEOUT: '0' })
|
||||
const secret = 'secret'
|
||||
const authData = {
|
||||
dropbox: { accessToken: 'token value' },
|
||||
box: { accessToken: 'token value' },
|
||||
drive: { accessToken: 'token value' },
|
||||
}
|
||||
const token = tokenService.generateEncryptedAuthToken(
|
||||
authData,
|
||||
process.env.COMPANION_SECRET,
|
||||
)
|
||||
const token = tokenService.generateEncryptedAuthToken(authData, secret)
|
||||
const OAUTH_STATE = 'some-cool-nice-encrytpion'
|
||||
|
||||
afterAll(() => {
|
||||
|
|
@ -49,7 +43,7 @@ afterAll(() => {
|
|||
})
|
||||
|
||||
describe('validate upload data', () => {
|
||||
test('access token expired or invalid when starting provider download', () => {
|
||||
test('access token expired or invalid when starting provider download', async () => {
|
||||
const meta = {
|
||||
size: null,
|
||||
mimeType: 'video/mp4',
|
||||
|
|
@ -73,7 +67,7 @@ describe('validate upload data', () => {
|
|||
},
|
||||
})
|
||||
|
||||
return request(authServer)
|
||||
return request(await getServerWithEnv())
|
||||
.post('/drive/get/DUMMY-FILE-ID')
|
||||
.set('uppy-auth-token', token)
|
||||
.set('Content-Type', 'application/json')
|
||||
|
|
@ -90,10 +84,10 @@ describe('validate upload data', () => {
|
|||
)
|
||||
})
|
||||
|
||||
test('invalid upload protocol gets rejected', () => {
|
||||
test('invalid upload protocol gets rejected', async () => {
|
||||
nockGoogleDownloadFile()
|
||||
|
||||
return request(authServer)
|
||||
return request(await getServerWithEnv())
|
||||
.post('/drive/get/DUMMY-FILE-ID')
|
||||
.set('uppy-auth-token', token)
|
||||
.set('Content-Type', 'application/json')
|
||||
|
|
@ -107,10 +101,10 @@ describe('validate upload data', () => {
|
|||
)
|
||||
})
|
||||
|
||||
test('invalid upload fieldname gets rejected', () => {
|
||||
test('invalid upload fieldname gets rejected', async () => {
|
||||
nockGoogleDownloadFile()
|
||||
|
||||
return request(authServer)
|
||||
return request(await getServerWithEnv())
|
||||
.post('/drive/get/DUMMY-FILE-ID')
|
||||
.set('uppy-auth-token', token)
|
||||
.set('Content-Type', 'application/json')
|
||||
|
|
@ -125,10 +119,10 @@ describe('validate upload data', () => {
|
|||
)
|
||||
})
|
||||
|
||||
test('invalid upload metadata gets rejected', () => {
|
||||
test('invalid upload metadata gets rejected', async () => {
|
||||
nockGoogleDownloadFile()
|
||||
|
||||
return request(authServer)
|
||||
return request(await getServerWithEnv())
|
||||
.post('/drive/get/DUMMY-FILE-ID')
|
||||
.set('uppy-auth-token', token)
|
||||
.set('Content-Type', 'application/json')
|
||||
|
|
@ -143,10 +137,10 @@ describe('validate upload data', () => {
|
|||
)
|
||||
})
|
||||
|
||||
test('invalid upload headers get rejected', () => {
|
||||
test('invalid upload headers get rejected', async () => {
|
||||
nockGoogleDownloadFile()
|
||||
|
||||
return request(authServer)
|
||||
return request(await getServerWithEnv())
|
||||
.post('/drive/get/DUMMY-FILE-ID')
|
||||
.set('uppy-auth-token', token)
|
||||
.set('Content-Type', 'application/json')
|
||||
|
|
@ -159,10 +153,10 @@ describe('validate upload data', () => {
|
|||
.then((res) => expect(res.body.message).toBe('headers must be an object'))
|
||||
})
|
||||
|
||||
test('invalid upload HTTP Method gets rejected', () => {
|
||||
test('invalid upload HTTP Method gets rejected', async () => {
|
||||
nockGoogleDownloadFile()
|
||||
|
||||
return request(authServer)
|
||||
return request(await getServerWithEnv())
|
||||
.post('/drive/get/DUMMY-FILE-ID')
|
||||
.set('uppy-auth-token', token)
|
||||
.set('Content-Type', 'application/json')
|
||||
|
|
@ -177,10 +171,10 @@ describe('validate upload data', () => {
|
|||
)
|
||||
})
|
||||
|
||||
test('valid upload data is allowed - tus', () => {
|
||||
test('valid upload data is allowed - tus', async () => {
|
||||
nockGoogleDownloadFile()
|
||||
|
||||
return request(authServer)
|
||||
return request(await getServerWithEnv())
|
||||
.post('/drive/get/DUMMY-FILE-ID')
|
||||
.set('uppy-auth-token', token)
|
||||
.set('Content-Type', 'application/json')
|
||||
|
|
@ -199,10 +193,10 @@ describe('validate upload data', () => {
|
|||
.expect(200)
|
||||
})
|
||||
|
||||
test('valid upload data is allowed - s3-multipart', () => {
|
||||
test('valid upload data is allowed - s3-multipart', async () => {
|
||||
nockGoogleDownloadFile()
|
||||
|
||||
return request(authServer)
|
||||
return request(await getServerWithEnv())
|
||||
.post('/drive/get/DUMMY-FILE-ID')
|
||||
.set('uppy-auth-token', token)
|
||||
.set('Content-Type', 'application/json')
|
||||
|
|
@ -222,8 +216,8 @@ describe('validate upload data', () => {
|
|||
})
|
||||
})
|
||||
|
||||
describe('handle main oauth redirect', () => {
|
||||
const serverWithMainOauth = getServer({
|
||||
describe('handle main oauth redirect', async () => {
|
||||
const serverWithMainOauth = await getServer({
|
||||
COMPANION_OAUTH_DOMAIN: 'localhost:3040',
|
||||
})
|
||||
test('redirect to a valid uppy instance', () => {
|
||||
|
|
@ -246,33 +240,36 @@ describe('handle main oauth redirect', () => {
|
|||
})
|
||||
})
|
||||
|
||||
it('periodically pings', (done) => {
|
||||
nock('http://localhost')
|
||||
.post(
|
||||
'/ping',
|
||||
(body) =>
|
||||
body.some === 'value' &&
|
||||
body.version === version &&
|
||||
typeof body.processId === 'string',
|
||||
)
|
||||
.reply(200, () => done())
|
||||
|
||||
getServer({
|
||||
COMPANION_PERIODIC_PING_URLS: 'http://localhost/ping',
|
||||
COMPANION_PERIODIC_PING_STATIC_JSON_PAYLOAD: '{"some": "value"}',
|
||||
COMPANION_PERIODIC_PING_INTERVAL: '10',
|
||||
COMPANION_PERIODIC_PING_COUNT: '1',
|
||||
})
|
||||
it('periodically pings', async () => {
|
||||
await Promise.all([
|
||||
getServer({
|
||||
COMPANION_PERIODIC_PING_URLS: 'http://localhost/ping',
|
||||
COMPANION_PERIODIC_PING_STATIC_JSON_PAYLOAD: '{"some": "value"}',
|
||||
COMPANION_PERIODIC_PING_INTERVAL: '10',
|
||||
COMPANION_PERIODIC_PING_COUNT: '1',
|
||||
}),
|
||||
new Promise((resolve) => {
|
||||
nock('http://localhost')
|
||||
.post(
|
||||
'/ping',
|
||||
(body) =>
|
||||
body.some === 'value' &&
|
||||
body.version === packageJson.version &&
|
||||
typeof body.processId === 'string',
|
||||
)
|
||||
.reply(200, () => resolve())
|
||||
}),
|
||||
])
|
||||
}, 3000)
|
||||
|
||||
async function runUrlMetaTest(url) {
|
||||
const server = getServer()
|
||||
const server = await getServer()
|
||||
|
||||
return request(server).post('/url/meta').send({ url })
|
||||
}
|
||||
|
||||
async function runUrlGetTest(url) {
|
||||
const server = getServer()
|
||||
const server = await getServer()
|
||||
|
||||
return request(server).post('/url/get').send({
|
||||
fileId: url,
|
||||
|
|
@ -1,6 +1,9 @@
|
|||
const { cors } = require('../../src/server/middlewares')
|
||||
import { describe, expect, test, vi } from 'vitest'
|
||||
|
||||
import { cors } from '../src/server/middlewares.js'
|
||||
|
||||
function testWithMock({
|
||||
// @ts-ignore
|
||||
corsOptions,
|
||||
get = () => {},
|
||||
origin = 'https://localhost:1234',
|
||||
|
|
@ -8,8 +11,8 @@ function testWithMock({
|
|||
const res = {
|
||||
get,
|
||||
getHeader: get,
|
||||
setHeader: jest.fn(),
|
||||
end: jest.fn(),
|
||||
setHeader: vi.fn(),
|
||||
end: vi.fn(),
|
||||
}
|
||||
const req = {
|
||||
method: 'OPTIONS',
|
||||
|
|
@ -17,7 +20,7 @@ function testWithMock({
|
|||
origin,
|
||||
},
|
||||
}
|
||||
const next = jest.fn()
|
||||
const next = vi.fn()
|
||||
cors(corsOptions)(req, res, next)
|
||||
return { res }
|
||||
}
|
||||
|
|
@ -35,6 +38,7 @@ describe('cors', () => {
|
|||
}
|
||||
|
||||
const { res } = testWithMock({
|
||||
// @ts-ignore
|
||||
corsOptions: {
|
||||
sendSelfEndpoint: true,
|
||||
corsOrigins: /^https:\/\/localhost:.*$/,
|
||||
|
|
@ -72,12 +76,14 @@ describe('cors', () => {
|
|||
})
|
||||
|
||||
test('should support disabling cors', () => {
|
||||
// @ts-ignore
|
||||
const { res } = testWithMock({ corsOptions: { corsOrigins: false } })
|
||||
expect(res.setHeader.mock.calls).toEqual([])
|
||||
})
|
||||
|
||||
test('should support incorrect url', () => {
|
||||
const { res } = testWithMock({
|
||||
// @ts-ignore
|
||||
corsOptions: { corsOrigins: /^incorrect$/ },
|
||||
})
|
||||
expect(res.setHeader.mock.calls).toEqual([
|
||||
|
|
@ -94,6 +100,7 @@ describe('cors', () => {
|
|||
|
||||
test('should support array origin', () => {
|
||||
const { res } = testWithMock({
|
||||
// @ts-ignore
|
||||
corsOptions: {
|
||||
corsOrigins: ['http://google.com', 'https://localhost:1234'],
|
||||
},
|
||||
|
|
@ -1,22 +1,25 @@
|
|||
const request = require('supertest')
|
||||
const nock = require('nock')
|
||||
const tokenService = require('../../src/server/helpers/jwt')
|
||||
const { getServer } = require('../mockserver')
|
||||
const { nockZoomRevoke } = require('../fixtures/zoom')
|
||||
import nock from 'nock'
|
||||
import request from 'supertest'
|
||||
import { afterAll, describe, expect, test, vi } from 'vitest'
|
||||
import * as tokenService from '../src/server/helpers/jwt.js'
|
||||
import { nockZoomRevoke, expects as zoomExpects } from './fixtures/zoom.js'
|
||||
import { getServer } from './mockserver.js'
|
||||
|
||||
const { remoteZoomKey, remoteZoomSecret, remoteZoomVerificationToken } =
|
||||
require('../fixtures/zoom').expects
|
||||
zoomExpects
|
||||
|
||||
const authServer = getServer({
|
||||
COMPANION_ZOOM_KEYS_ENDPOINT: 'http://localhost:2111/zoom-keys',
|
||||
})
|
||||
vi.mock('express-prom-bundle')
|
||||
|
||||
const secret = 'secret'
|
||||
|
||||
const getZoomServer = async () =>
|
||||
getServer({
|
||||
COMPANION_ZOOM_KEYS_ENDPOINT: 'http://localhost:2111/zoom-keys',
|
||||
})
|
||||
const authData = {
|
||||
zoom: { accessToken: 'token value' },
|
||||
}
|
||||
const token = tokenService.generateEncryptedAuthToken(
|
||||
authData,
|
||||
process.env.COMPANION_SECRET,
|
||||
)
|
||||
const token = tokenService.generateEncryptedAuthToken(authData, secret)
|
||||
|
||||
afterAll(() => {
|
||||
nock.cleanAll()
|
||||
|
|
@ -27,6 +30,7 @@ describe('providers requests with remote oauth keys', () => {
|
|||
// mocking request module used to fetch custom oauth credentials
|
||||
nock('http://localhost:2111')
|
||||
.post('/zoom-keys')
|
||||
// @ts-ignore
|
||||
.reply((uri, { provider, parameters }) => {
|
||||
if (provider !== 'zoom' || parameters !== 'ZOOM-CREDENTIALS-PARAMS')
|
||||
return [400]
|
||||
|
|
@ -52,7 +56,7 @@ describe('providers requests with remote oauth keys', () => {
|
|||
JSON.stringify(params),
|
||||
'binary',
|
||||
).toString('base64')
|
||||
const res = await request(authServer)
|
||||
const res = await request(await getZoomServer())
|
||||
.get('/zoom/logout/')
|
||||
.set('uppy-auth-token', token)
|
||||
.set('uppy-credentials-params', encodedParams)
|
||||
|
|
@ -64,13 +68,13 @@ describe('providers requests with remote oauth keys', () => {
|
|||
})
|
||||
})
|
||||
|
||||
test('zoom logout with wrong credentials params', () => {
|
||||
test('zoom logout with wrong credentials params', async () => {
|
||||
const params = { params: 'WRONG-ZOOM-CREDENTIALS-PARAMS' }
|
||||
const encodedParams = Buffer.from(
|
||||
JSON.stringify(params),
|
||||
'binary',
|
||||
).toString('base64')
|
||||
return request(authServer)
|
||||
return request(await getZoomServer())
|
||||
.get('/zoom/logout/')
|
||||
.set('uppy-auth-token', token)
|
||||
.set('uppy-credentials-params', encodedParams)
|
||||
|
|
@ -1,8 +1,9 @@
|
|||
const nock = require('nock')
|
||||
const request = require('supertest')
|
||||
const { getServer } = require('../mockserver')
|
||||
import nock from 'nock'
|
||||
import request from 'supertest'
|
||||
import { afterAll, describe, test, vi } from 'vitest'
|
||||
import { getServer } from './mockserver.js'
|
||||
|
||||
const authServer = getServer()
|
||||
vi.mock('express-prom-bundle')
|
||||
|
||||
afterAll(() => {
|
||||
nock.cleanAll()
|
||||
|
|
@ -12,8 +13,8 @@ afterAll(() => {
|
|||
describe('handle deauthorization callback', () => {
|
||||
nock('https://api.zoom.us').post('/oauth/data/compliance').reply(200)
|
||||
|
||||
test('providers without support for callback endpoint', () => {
|
||||
return request(authServer)
|
||||
test('providers without support for callback endpoint', async () => {
|
||||
return request(await getServer())
|
||||
.post('/dropbox/deauthorization/callback')
|
||||
.set('Content-Type', 'application/json')
|
||||
.send({
|
||||
|
|
@ -22,8 +23,8 @@ describe('handle deauthorization callback', () => {
|
|||
.expect(500)
|
||||
})
|
||||
|
||||
test('validate that request credentials match', () => {
|
||||
return request(authServer)
|
||||
test('validate that request credentials match', async () => {
|
||||
return request(await getServer())
|
||||
.post('/zoom/deauthorization/callback')
|
||||
.set('Content-Type', 'application/json')
|
||||
.set('Authorization', 'wrong-verfication-token')
|
||||
|
|
@ -42,9 +43,9 @@ describe('handle deauthorization callback', () => {
|
|||
.expect(400)
|
||||
})
|
||||
|
||||
test('validate request credentials is present', () => {
|
||||
test('validate request credentials is present', async () => {
|
||||
// Authorization header is absent
|
||||
return request(authServer)
|
||||
return request(await getServer())
|
||||
.post('/zoom/deauthorization/callback')
|
||||
.set('Content-Type', 'application/json')
|
||||
.send({
|
||||
|
|
@ -62,8 +63,8 @@ describe('handle deauthorization callback', () => {
|
|||
.expect(400)
|
||||
})
|
||||
|
||||
test('validate request content', () => {
|
||||
return request(authServer)
|
||||
test('validate request content', async () => {
|
||||
return request(await getServer())
|
||||
.post('/zoom/deauthorization/callback')
|
||||
.set('Content-Type', 'application/json')
|
||||
.set('Authorization', 'zoom_verfication_token')
|
||||
|
|
@ -73,8 +74,8 @@ describe('handle deauthorization callback', () => {
|
|||
.expect(400)
|
||||
})
|
||||
|
||||
test('validate request content (event name)', () => {
|
||||
return request(authServer)
|
||||
test('validate request content (event name)', async () => {
|
||||
return request(await getServer())
|
||||
.post('/zoom/deauthorization/callback')
|
||||
.set('Content-Type', 'application/json')
|
||||
.set('Authorization', 'zoom_verfication_token')
|
||||
|
|
@ -93,8 +94,8 @@ describe('handle deauthorization callback', () => {
|
|||
.expect(400)
|
||||
})
|
||||
|
||||
test('allow valid request', () => {
|
||||
return request(authServer)
|
||||
test('allow valid request', async () => {
|
||||
return request(await getServer())
|
||||
.post('/zoom/deauthorization/callback')
|
||||
.set('Content-Type', 'application/json')
|
||||
.set('Authorization', 'zoom_verfication_token')
|
||||
|
|
@ -1,3 +1,3 @@
|
|||
module.exports.expects = {
|
||||
export const expects = {
|
||||
itemIcon: 'file',
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
module.exports.NEXT_PAGE_TOKEN = 'DUMMY-NEXT-PAGE-TOKEN'
|
||||
module.exports.ITEM_ID = 'DUMMY-FILE-ID'
|
||||
module.exports.ITEM_NAME = 'MY DUMMY FILE NAME.mp4'
|
||||
module.exports.ICON = 'https://DUMMY-THUMBNAIL.com/file.jpg'
|
||||
module.exports.THUMBNAIL_URL = 'https://DUMMY-THUMBNAIL.com/file.jpg'
|
||||
module.exports.MODIFIED_DATE = '2016-07-10T20:00:08.096Z'
|
||||
module.exports.MIME_TYPE = 'video/mp4'
|
||||
module.exports.USERNAME = 'john.doe@transloadit.com'
|
||||
module.exports.FILE_SIZE = 758051
|
||||
export const NEXT_PAGE_TOKEN = 'DUMMY-NEXT-PAGE-TOKEN'
|
||||
export const ITEM_ID = 'DUMMY-FILE-ID'
|
||||
export const ITEM_NAME = 'MY DUMMY FILE NAME.mp4'
|
||||
export const ICON = 'https://DUMMY-THUMBNAIL.com/file.jpg'
|
||||
export const THUMBNAIL_URL = 'https://DUMMY-THUMBNAIL.com/file.jpg'
|
||||
export const MODIFIED_DATE = '2016-07-10T20:00:08.096Z'
|
||||
export const MIME_TYPE = 'video/mp4'
|
||||
export const USERNAME = 'john.doe@transloadit.com'
|
||||
export const FILE_SIZE = 758051
|
||||
|
|
|
|||
12
packages/@uppy/companion/test/fixtures/drive.js
vendored
12
packages/@uppy/companion/test/fixtures/drive.js
vendored
|
|
@ -1,14 +1,14 @@
|
|||
const nock = require('nock')
|
||||
const defaults = require('./constants')
|
||||
import nock from 'nock'
|
||||
import * as defaults from './constants.js'
|
||||
|
||||
module.exports.expects = {}
|
||||
export const expects = {}
|
||||
|
||||
module.exports.nockGoogleDriveAboutCall = () =>
|
||||
export const nockGoogleDriveAboutCall = () =>
|
||||
nock('https://www.googleapis.com')
|
||||
.get((uri) => uri.includes('about'))
|
||||
.reply(200, { user: { emailAddress: 'john.doe@transloadit.com' } })
|
||||
|
||||
module.exports.nockGoogleDownloadFile = ({ times = 2 } = {}) => {
|
||||
export const nockGoogleDownloadFile = ({ times = 2 } = {}) => {
|
||||
nock('https://www.googleapis.com')
|
||||
.get(
|
||||
`/drive/v3/files/${defaults.ITEM_ID}?fields=kind%2Cid%2CimageMediaMetadata%2Cname%2CmimeType%2CownedByMe%2Csize%2CmodifiedTime%2CiconLink%2CthumbnailLink%2CteamDriveId%2CvideoMediaMetadata%2CexportLinks%2CshortcutDetails%28targetId%2CtargetMimeType%29&supportsAllDrives=true`,
|
||||
|
|
@ -32,5 +32,5 @@ module.exports.nockGoogleDownloadFile = ({ times = 2 } = {}) => {
|
|||
nock('https://www.googleapis.com')
|
||||
.get(`/drive/v3/files/${defaults.ITEM_ID}?alt=media&supportsAllDrives=true`)
|
||||
.reply(200, {})
|
||||
module.exports.nockGoogleDriveAboutCall()
|
||||
nockGoogleDriveAboutCall()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
module.exports.expects = {
|
||||
export const expects = {
|
||||
itemIcon: 'file',
|
||||
itemRequestPath: '%2Fhomework%2Fmath%2Fprime_numbers.txt',
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
const defaults = require('./constants')
|
||||
import * as defaults from './constants.js'
|
||||
|
||||
module.exports.expects = {
|
||||
export const expects = {
|
||||
listPath: 'ALBUM-ID',
|
||||
itemName: `${defaults.ITEM_ID} 2015-07-17T17:26:50+0000`,
|
||||
itemMimeType: 'image/jpeg',
|
||||
|
|
|
|||
19
packages/@uppy/companion/test/fixtures/index.js
vendored
19
packages/@uppy/companion/test/fixtures/index.js
vendored
|
|
@ -1,12 +1,13 @@
|
|||
const box = require('./box')
|
||||
const drive = require('./drive')
|
||||
const dropbox = require('./dropbox')
|
||||
const instagram = require('./instagram')
|
||||
const onedrive = require('./onedrive')
|
||||
const facebook = require('./facebook')
|
||||
const zoom = require('./zoom')
|
||||
import * as box from './box.js'
|
||||
import * as constants from './constants.js'
|
||||
import * as drive from './drive.js'
|
||||
import * as dropbox from './dropbox.js'
|
||||
import * as facebook from './facebook.js'
|
||||
import * as instagram from './instagram.js'
|
||||
import * as onedrive from './onedrive.js'
|
||||
import * as zoom from './zoom.js'
|
||||
|
||||
module.exports.providers = {
|
||||
export const providers = {
|
||||
box,
|
||||
drive,
|
||||
dropbox,
|
||||
|
|
@ -16,4 +17,4 @@ module.exports.providers = {
|
|||
zoom,
|
||||
}
|
||||
|
||||
module.exports.defaults = require('./constants')
|
||||
export const defaults = constants
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
module.exports.expects = {
|
||||
export const expects = {
|
||||
itemName: 'Instagram 2017-08-31T18:10:00+00000.jpeg',
|
||||
itemMimeType: 'image/jpeg',
|
||||
itemSize: null,
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
const defaults = require('./constants')
|
||||
import * as defaults from './constants.js'
|
||||
|
||||
module.exports.expects = {
|
||||
export const expects = {
|
||||
itemRequestPath: `${defaults.ITEM_ID}?driveId=DUMMY-DRIVE-ID`,
|
||||
}
|
||||
|
|
|
|||
12
packages/@uppy/companion/test/fixtures/zoom.js
vendored
12
packages/@uppy/companion/test/fixtures/zoom.js
vendored
|
|
@ -1,8 +1,7 @@
|
|||
const nock = require('nock')
|
||||
import nock from 'nock'
|
||||
import { getBasicAuthHeader } from '../../src/server/helpers/utils.js'
|
||||
|
||||
const { getBasicAuthHeader } = require('../../src/server/helpers/utils')
|
||||
|
||||
module.exports.expects = {
|
||||
export const expects = {
|
||||
listPath: 'DUMMY-UUID%3D%3D',
|
||||
itemName:
|
||||
'DUMMY TOPIC - shared screen with speaker view (2020-05-29, 13:23).mp4',
|
||||
|
|
@ -17,7 +16,7 @@ module.exports.expects = {
|
|||
remoteZoomVerificationToken: 'REMOTE-ZOOM-VERIFICATION-TOKEN',
|
||||
}
|
||||
|
||||
module.exports.nockZoomRecordings = ({ times = 1 } = {}) => {
|
||||
export const nockZoomRecordings = ({ times = 1 } = {}) => {
|
||||
nock('https://zoom.us')
|
||||
.get('/v2/meetings/DUMMY-UUID%3D%3D/recordings')
|
||||
.times(times)
|
||||
|
|
@ -51,12 +50,11 @@ module.exports.nockZoomRecordings = ({ times = 1 } = {}) => {
|
|||
})
|
||||
}
|
||||
|
||||
module.exports.nockZoomRevoke = ({ key, secret }) => {
|
||||
export const nockZoomRevoke = ({ key, secret }) => {
|
||||
nock('https://zoom.us')
|
||||
.post('/oauth/revoke?token=token+value')
|
||||
.reply(function () {
|
||||
const { headers } = this.req
|
||||
|
||||
const expected = getBasicAuthHeader(key, secret)
|
||||
const success = headers.authorization === expected
|
||||
return success ? [200, { status: 'success' }] : [400]
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
const headerSanitize = require('../../src/server/header-blacklist')
|
||||
import { describe, expect, test } from 'vitest'
|
||||
import headerSanitize from '../src/server/header-blacklist.js'
|
||||
|
||||
describe('Header black-list testing', () => {
|
||||
test('All headers invalid by name', () => {
|
||||
|
|
@ -1,6 +1,9 @@
|
|||
const nock = require('nock')
|
||||
const { FORBIDDEN_IP_ADDRESS } = require('../../src/server/helpers/request')
|
||||
const { getProtectedGot } = require('../../src/server/helpers/request')
|
||||
import nock from 'nock'
|
||||
import { afterAll, describe, expect, test } from 'vitest'
|
||||
import {
|
||||
FORBIDDEN_IP_ADDRESS,
|
||||
getProtectedGot,
|
||||
} from '../src/server/helpers/request.js'
|
||||
|
||||
afterAll(() => {
|
||||
nock.cleanAll()
|
||||
|
|
@ -11,31 +14,28 @@ describe('test protected request Agent', () => {
|
|||
test('allows URLs without IP addresses', async () => {
|
||||
nock('https://transloadit.com').get('/').reply(200)
|
||||
const url = 'https://transloadit.com'
|
||||
return (await getProtectedGot({ allowLocalIPs: false })).get(url)
|
||||
return getProtectedGot({ allowLocalIPs: false }).get(url)
|
||||
})
|
||||
|
||||
test('blocks url that resolves to forbidden IP', async () => {
|
||||
const url = 'https://localhost'
|
||||
const promise = getProtectedGot({ allowLocalIPs: false }).then((got) =>
|
||||
got.get(url),
|
||||
)
|
||||
await expect(promise).rejects.toThrow(/^Forbidden resolved IP address/)
|
||||
await expect(
|
||||
getProtectedGot({ allowLocalIPs: false }).get(url),
|
||||
).rejects.toThrow(/^Forbidden resolved IP address/)
|
||||
})
|
||||
|
||||
test('blocks private http IP address', async () => {
|
||||
const url = 'http://172.20.10.4:8090'
|
||||
const promise = getProtectedGot({ allowLocalIPs: false }).then((got) =>
|
||||
got.get(url),
|
||||
)
|
||||
await expect(promise).rejects.toThrow(new Error(FORBIDDEN_IP_ADDRESS))
|
||||
await expect(
|
||||
getProtectedGot({ allowLocalIPs: false }).get(url),
|
||||
).rejects.toThrow(FORBIDDEN_IP_ADDRESS)
|
||||
})
|
||||
|
||||
test('blocks private https IP address', async () => {
|
||||
const url = 'https://172.20.10.4:8090'
|
||||
const promise = getProtectedGot({ allowLocalIPs: false }).then((got) =>
|
||||
got.get(url),
|
||||
)
|
||||
await expect(promise).rejects.toThrow(new Error(FORBIDDEN_IP_ADDRESS))
|
||||
await expect(
|
||||
getProtectedGot({ allowLocalIPs: false }).get(url),
|
||||
).rejects.toThrow(FORBIDDEN_IP_ADDRESS)
|
||||
})
|
||||
|
||||
test('blocks various private IP addresses', async () => {
|
||||
|
|
@ -62,17 +62,15 @@ describe('test protected request Agent', () => {
|
|||
|
||||
for (const ip of ipv4s) {
|
||||
const url = `http://${ip}:8090`
|
||||
const promise = getProtectedGot({ allowLocalIPs: false }).then((got) =>
|
||||
got.get(url),
|
||||
)
|
||||
await expect(promise).rejects.toThrow(new Error(FORBIDDEN_IP_ADDRESS))
|
||||
await expect(
|
||||
getProtectedGot({ allowLocalIPs: false }).get(url),
|
||||
).rejects.toThrow(FORBIDDEN_IP_ADDRESS)
|
||||
}
|
||||
for (const ip of ipv6s) {
|
||||
const url = `http://[${ip}]:8090`
|
||||
const promise = getProtectedGot({ allowLocalIPs: false }).then((got) =>
|
||||
got.get(url),
|
||||
)
|
||||
await expect(promise).rejects.toThrow(new Error(FORBIDDEN_IP_ADDRESS))
|
||||
await expect(
|
||||
getProtectedGot({ allowLocalIPs: false }).get(url),
|
||||
).rejects.toThrow(FORBIDDEN_IP_ADDRESS)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
|
@ -1,6 +1,9 @@
|
|||
import { beforeAll, describe, expect, test } from 'vitest'
|
||||
|
||||
// We don't care about colors in our tests, so force `supports-color` to disable colors.
|
||||
process.env.FORCE_COLOR = 'false'
|
||||
const logger = require('../../src/server/logger')
|
||||
|
||||
import logger from '../src/server/logger.js'
|
||||
|
||||
const maskables = ['ToBeMasked1', 'toBeMasked2', 'toBeMasked(And)?Escaped']
|
||||
|
||||
|
|
@ -104,6 +107,7 @@ describe('Test Logger secret mask', () => {
|
|||
|
||||
test('masks inside object', () => {
|
||||
const loggedMessage = captureConsoleLog(() => {
|
||||
// @ts-ignore
|
||||
logger.warn({
|
||||
a: 1,
|
||||
deep: { secret: 'there is a ToBeMasked1 hiding here' },
|
||||
|
|
@ -1,7 +1,9 @@
|
|||
module.exports = () => {
|
||||
return {
|
||||
generateState: () => 'some-cool-nice-encrytpion',
|
||||
addToState: () => 'some-cool-nice-encrytpion',
|
||||
import { vi } from 'vitest'
|
||||
|
||||
const mockOauthState = () => {
|
||||
vi.mock('../src/server/helpers/oauth-state.js', async () => ({
|
||||
...(await vi.importActual('../src/server/helpers/oauth-state.js')),
|
||||
generateState: () => ({}),
|
||||
getFromState: (state) => {
|
||||
if (state === 'state-with-invalid-instance-url') {
|
||||
return 'http://localhost:3452'
|
||||
|
|
@ -10,5 +12,7 @@ module.exports = () => {
|
|||
return 'http://localhost:3020'
|
||||
},
|
||||
encodeState: () => 'some-cool-nice-encrytpion',
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
||||
export default mockOauthState
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
const express = require('express')
|
||||
const session = require('express-session')
|
||||
import express from 'express'
|
||||
import session from 'express-session'
|
||||
import { expects as zoomExpects } from './fixtures/zoom.js'
|
||||
|
||||
const {
|
||||
expects: { localZoomKey, localZoomSecret, localZoomVerificationToken },
|
||||
} = require('./fixtures/zoom')
|
||||
const { localZoomKey, localZoomSecret, localZoomVerificationToken } =
|
||||
zoomExpects
|
||||
|
||||
const defaultEnv = {
|
||||
NODE_ENV: 'test',
|
||||
|
|
@ -24,22 +24,28 @@ const defaultEnv = {
|
|||
|
||||
COMPANION_DROPBOX_KEY: 'dropbox_key',
|
||||
COMPANION_DROPBOX_SECRET: 'dropbox_secret',
|
||||
COMPANION_DROPBOX_KEYS_ENDPOINT: undefined,
|
||||
|
||||
COMPANION_BOX_KEY: 'box_key',
|
||||
COMPANION_BOX_SECRET: 'box_secret',
|
||||
COMPANION_BOX_KEYS_ENDPOINT: undefined,
|
||||
|
||||
COMPANION_GOOGLE_KEY: 'google_key',
|
||||
COMPANION_GOOGLE_SECRET: 'google_secret',
|
||||
COMPANION_GOOGLE_KEYS_ENDPOINT: undefined,
|
||||
|
||||
COMPANION_INSTAGRAM_KEY: 'instagram_key',
|
||||
COMPANION_INSTAGRAM_SECRET: 'instagram_secret',
|
||||
COMPANION_INSTAGRAM_KEYS_ENDPOINT: undefined,
|
||||
|
||||
COMPANION_FACEBOOK_KEY: 'facebook_key',
|
||||
COMPANION_FACEBOOK_SECRET: 'facebook_secret',
|
||||
COMPANION_FACEBOOK_KEYS_ENDPOINT: undefined,
|
||||
|
||||
COMPANION_ZOOM_KEY: localZoomKey,
|
||||
COMPANION_ZOOM_SECRET: localZoomSecret,
|
||||
COMPANION_ZOOM_VERIFICATION_TOKEN: localZoomVerificationToken,
|
||||
COMPANION_ZOOM_KEYS_ENDPOINT: undefined,
|
||||
|
||||
COMPANION_PATH: '',
|
||||
|
||||
|
|
@ -54,15 +60,22 @@ const defaultEnv = {
|
|||
|
||||
function updateEnv(env) {
|
||||
Object.keys(env).forEach((key) => {
|
||||
process.env[key] = env[key]
|
||||
const value = env[key]
|
||||
if (value == null) delete process.env[key]
|
||||
else process.env[key] = value
|
||||
})
|
||||
}
|
||||
|
||||
module.exports.setDefaultEnv = () => updateEnv(defaultEnv)
|
||||
export const setDefaultEnv = () => updateEnv(defaultEnv)
|
||||
|
||||
module.exports.grantToken = 'fake token'
|
||||
export const grantToken = 'fake token'
|
||||
|
||||
// companion stores certain global state, so the user needs to reset modules for each test
|
||||
// todo rewrite companion to not use global state
|
||||
// https://github.com/transloadit/uppy/issues/3284
|
||||
export const getServer = async (extraEnv) => {
|
||||
const { default: standalone } = await import('../src/standalone/index.js')
|
||||
|
||||
module.exports.getServer = (extraEnv) => {
|
||||
const env = {
|
||||
...defaultEnv,
|
||||
...extraEnv,
|
||||
|
|
@ -70,23 +83,20 @@ module.exports.getServer = (extraEnv) => {
|
|||
|
||||
updateEnv(env)
|
||||
|
||||
// companion stores certain global state like emitter, metrics, logger (frozen object), so we need to reset modules
|
||||
// todo rewrite companion to not use global state
|
||||
// https://github.com/transloadit/uppy/issues/3284
|
||||
jest.resetModules()
|
||||
const standalone = require('../src/standalone')
|
||||
const authServer = express()
|
||||
|
||||
authServer.use(
|
||||
session({ secret: 'grant', resave: true, saveUninitialized: true }),
|
||||
)
|
||||
authServer.all('*/callback', (req, res, next) => {
|
||||
// @ts-ignore
|
||||
req.session.grant = {
|
||||
response: { access_token: module.exports.grantToken },
|
||||
response: { access_token: grantToken },
|
||||
}
|
||||
next()
|
||||
})
|
||||
authServer.all(['*/send-token', '*/redirect'], (req, res, next) => {
|
||||
// @ts-ignore
|
||||
req.session.grant = {
|
||||
dynamic: { state: req.query.state || 'non-empty-value' },
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
const emitter = require('../src/server/emitter')
|
||||
import emitter from '../src/server/emitter/index.js'
|
||||
|
||||
module.exports.connect = (uploadToken) => {
|
||||
export const connect = (uploadToken) => {
|
||||
emitter().emit(`connection:${uploadToken}`)
|
||||
}
|
||||
|
||||
module.exports.onProgress = (uploadToken, cb) => {
|
||||
export const onProgress = (uploadToken, cb) => {
|
||||
emitter().on(uploadToken, (message) => {
|
||||
if (message.action === 'progress') {
|
||||
cb(message)
|
||||
|
|
@ -12,7 +12,7 @@ module.exports.onProgress = (uploadToken, cb) => {
|
|||
})
|
||||
}
|
||||
|
||||
module.exports.onUploadSuccess = (uploadToken, cb) => {
|
||||
export const onUploadSuccess = (uploadToken, cb) => {
|
||||
emitter().on(uploadToken, (message) => {
|
||||
if (message.action === 'success') {
|
||||
cb(message)
|
||||
|
|
@ -20,7 +20,7 @@ module.exports.onUploadSuccess = (uploadToken, cb) => {
|
|||
})
|
||||
}
|
||||
|
||||
module.exports.onUploadError = (uploadToken, cb) => {
|
||||
export const onUploadError = (uploadToken, cb) => {
|
||||
emitter().on(uploadToken, (message) => {
|
||||
if (message.action === 'error') {
|
||||
cb(message)
|
||||
|
|
|
|||
|
|
@ -1,4 +1,10 @@
|
|||
jest.mock('../../src/server/helpers/jwt', () => {
|
||||
import request from 'supertest'
|
||||
import { describe, expect, test, vi } from 'vitest'
|
||||
import { getServer } from './mockserver.js'
|
||||
|
||||
vi.mock('express-prom-bundle')
|
||||
|
||||
vi.mock('../src/server/helpers/jwt.js', () => {
|
||||
return {
|
||||
generateEncryptedToken: () => 'dummy token',
|
||||
verifyEncryptedToken: () => '',
|
||||
|
|
@ -7,19 +13,15 @@ jest.mock('../../src/server/helpers/jwt', () => {
|
|||
}
|
||||
})
|
||||
|
||||
const request = require('supertest')
|
||||
const { getServer } = require('../mockserver')
|
||||
// the order in which getServer is called matters because, once an env is passed,
|
||||
// it won't be overridden when you call getServer without an argument
|
||||
const serverWithFixedOauth = getServer()
|
||||
const serverWithDynamicOauth = getServer({
|
||||
COMPANION_DROPBOX_KEYS_ENDPOINT: 'http://localhost:1000/endpoint',
|
||||
})
|
||||
const getServerWithDynamicOauth = async () =>
|
||||
getServer({
|
||||
COMPANION_DROPBOX_KEYS_ENDPOINT: 'http://localhost:1000/endpoint',
|
||||
})
|
||||
|
||||
describe('handle preauth endpoint', () => {
|
||||
test('happy path', () => {
|
||||
test('happy path', async () => {
|
||||
return (
|
||||
request(serverWithDynamicOauth)
|
||||
request(await getServerWithDynamicOauth())
|
||||
.post('/dropbox/preauth')
|
||||
.set('Content-Type', 'application/json')
|
||||
.send({
|
||||
|
|
@ -31,8 +33,8 @@ describe('handle preauth endpoint', () => {
|
|||
)
|
||||
})
|
||||
|
||||
test('preauth request without params in body', () => {
|
||||
return request(serverWithDynamicOauth)
|
||||
test('preauth request without params in body', async () => {
|
||||
return request(await getServerWithDynamicOauth())
|
||||
.post('/dropbox/preauth')
|
||||
.set('Content-Type', 'application/json')
|
||||
.send({
|
||||
|
|
@ -41,8 +43,8 @@ describe('handle preauth endpoint', () => {
|
|||
.expect(400)
|
||||
})
|
||||
|
||||
test('providers with dynamic credentials disabled', () => {
|
||||
return request(serverWithDynamicOauth)
|
||||
test('providers with dynamic credentials disabled', async () => {
|
||||
return request(await getServerWithDynamicOauth())
|
||||
.post('/drive/preauth')
|
||||
.set('Content-Type', 'application/json')
|
||||
.send({
|
||||
|
|
@ -51,8 +53,8 @@ describe('handle preauth endpoint', () => {
|
|||
.expect(501)
|
||||
})
|
||||
|
||||
test('server with dynamic credentials disabled', () => {
|
||||
return request(serverWithFixedOauth)
|
||||
test('server with dynamic credentials disabled', async () => {
|
||||
return request(await getServer())
|
||||
.post('/dropbox/preauth')
|
||||
.set('Content-Type', 'application/json')
|
||||
.send({
|
||||
|
|
@ -1,6 +1,8 @@
|
|||
const providerManager = require('../../src/server/provider')
|
||||
const { getCompanionOptions } = require('../../src/standalone/helper')
|
||||
const { setDefaultEnv } = require('../mockserver')
|
||||
import { beforeEach, describe, expect, test } from 'vitest'
|
||||
import GrantConfig from '../src/config/grant.js'
|
||||
import * as providerManager from '../src/server/provider/index.js'
|
||||
import { getCompanionOptions } from '../src/standalone/helper.js'
|
||||
import { setDefaultEnv } from './mockserver.js'
|
||||
|
||||
let grantConfig
|
||||
let companionOptions
|
||||
|
|
@ -11,7 +13,7 @@ const getOauthProvider = (providerName) =>
|
|||
describe('Test Provider options', () => {
|
||||
beforeEach(() => {
|
||||
setDefaultEnv()
|
||||
grantConfig = require('../../src/config/grant')()
|
||||
grantConfig = GrantConfig()
|
||||
companionOptions = getCompanionOptions()
|
||||
})
|
||||
|
||||
|
|
@ -198,6 +200,7 @@ describe('Test Custom Provider options', () => {
|
|||
key: 'foo_key',
|
||||
secret: 'foo_secret',
|
||||
},
|
||||
// @ts-ignore
|
||||
module: { oauthProvider: 'some_provider' },
|
||||
},
|
||||
},
|
||||
|
|
@ -1,32 +1,39 @@
|
|||
const request = require('supertest')
|
||||
const nock = require('nock')
|
||||
import nock from 'nock'
|
||||
import request from 'supertest'
|
||||
import { afterAll, beforeAll, describe, expect, test, vi } from 'vitest'
|
||||
import * as tokenService from '../src/server/helpers/jwt.js'
|
||||
import * as providerModule from '../src/server/provider/index.js'
|
||||
import * as defaults from './fixtures/constants.js'
|
||||
import { nockGoogleDownloadFile } from './fixtures/drive.js'
|
||||
import * as fixtures from './fixtures/index.js'
|
||||
import {
|
||||
nockZoomRecordings,
|
||||
nockZoomRevoke,
|
||||
expects as zoomExpects,
|
||||
} from './fixtures/zoom.js'
|
||||
import mockOauthState from './mockoauthstate.js'
|
||||
import { getServer } from './mockserver.js'
|
||||
|
||||
const mockOauthState = require('../mockoauthstate')
|
||||
const { localZoomKey, localZoomSecret } = zoomExpects
|
||||
|
||||
jest.mock('tus-js-client')
|
||||
jest.mock('../../src/server/helpers/request', () => {
|
||||
vi.mock('express-prom-bundle')
|
||||
vi.mock('tus-js-client')
|
||||
|
||||
mockOauthState()
|
||||
|
||||
vi.mock('../../src/server/helpers/request.js', () => {
|
||||
return {
|
||||
getURLMeta: () => Promise.resolve({ size: 758051 }),
|
||||
}
|
||||
})
|
||||
jest.mock('../../src/server/helpers/oauth-state', () => mockOauthState())
|
||||
|
||||
const fixtures = require('../fixtures')
|
||||
const { nockGoogleDownloadFile } = require('../fixtures/drive')
|
||||
const {
|
||||
nockZoomRecordings,
|
||||
nockZoomRevoke,
|
||||
expects: { localZoomKey, localZoomSecret },
|
||||
} = require('../fixtures/zoom')
|
||||
const defaults = require('../fixtures/constants')
|
||||
|
||||
const tokenService = require('../../src/server/helpers/jwt')
|
||||
const { getServer } = require('../mockserver')
|
||||
|
||||
// todo don't share server between tests. rewrite to not use env variables
|
||||
const authServer = getServer({ COMPANION_CLIENT_SOCKET_CONNECT_TIMEOUT: '0' })
|
||||
const getServerWithEnv = async () =>
|
||||
getServer({ COMPANION_CLIENT_SOCKET_CONNECT_TIMEOUT: '0' })
|
||||
const OAUTH_STATE = 'some-cool-nice-encrytpion'
|
||||
const providers = require('../../src/server/provider').getDefaultProviders()
|
||||
|
||||
const secret = 'secret'
|
||||
|
||||
const providers = providerModule.getDefaultProviders()
|
||||
|
||||
const providerNames = Object.keys(providers)
|
||||
const oauthProviders = Object.fromEntries(
|
||||
|
|
@ -38,10 +45,7 @@ const authData = {}
|
|||
providerNames.forEach((provider) => {
|
||||
authData[provider] = { accessToken: 'token value' }
|
||||
})
|
||||
const token = tokenService.generateEncryptedAuthToken(
|
||||
authData,
|
||||
process.env.COMPANION_SECRET,
|
||||
)
|
||||
const token = tokenService.generateEncryptedAuthToken(authData, secret)
|
||||
|
||||
const thisOrThat = (value1, value2) => {
|
||||
if (value1 !== undefined) {
|
||||
|
|
@ -88,7 +92,7 @@ afterAll(() => {
|
|||
describe('list provider files', () => {
|
||||
async function runTest(providerName) {
|
||||
const providerFixture = fixtures.providers[providerName]?.expects ?? {}
|
||||
return request(authServer)
|
||||
return request(await getServerWithEnv())
|
||||
.get(`/${providerName}/list/${providerFixture.listPath || ''}`)
|
||||
.set('uppy-auth-token', token)
|
||||
.expect(200)
|
||||
|
|
@ -376,7 +380,7 @@ describe('list provider files', () => {
|
|||
describe('provider file gets downloaded from', () => {
|
||||
async function runTest(providerName) {
|
||||
const providerFixture = fixtures.providers[providerName]?.expects ?? {}
|
||||
const res = await request(authServer)
|
||||
const res = await request(await getServerWithEnv())
|
||||
.post(
|
||||
`/${providerName}/get/${providerFixture.itemRequestPath || defaults.ITEM_ID}`,
|
||||
)
|
||||
|
|
@ -492,7 +496,7 @@ describe('connect to provider', () => {
|
|||
|
||||
if (oauthProvider == null) return
|
||||
|
||||
await request(authServer)
|
||||
await request(await getServerWithEnv())
|
||||
.get(`/${providerName}/connect?foo=bar`)
|
||||
.set('uppy-auth-token', token)
|
||||
.expect(302)
|
||||
|
|
@ -506,7 +510,7 @@ describe('connect to provider', () => {
|
|||
|
||||
describe('logout of provider', () => {
|
||||
async function runTest(providerName) {
|
||||
const res = await request(authServer)
|
||||
const res = await request(await getServerWithEnv())
|
||||
.get(`/${providerName}/logout/`)
|
||||
.set('uppy-auth-token', token)
|
||||
.expect(200)
|
||||
|
|
@ -1,8 +1,11 @@
|
|||
const request = require('supertest')
|
||||
const { getServer } = require('../mockserver')
|
||||
import request from 'supertest'
|
||||
import { it, test, vi } from 'vitest'
|
||||
import { getServer } from './mockserver.js'
|
||||
|
||||
vi.mock('express-prom-bundle')
|
||||
|
||||
it('can be served under a subpath', async () => {
|
||||
const server = getServer({ COMPANION_PATH: '/subpath' })
|
||||
const server = await getServer({ COMPANION_PATH: '/subpath' })
|
||||
|
||||
await request(server).get('/subpath').expect(200)
|
||||
await request(server).get('/subpath/metrics').expect(200)
|
||||
|
|
@ -11,7 +14,7 @@ it('can be served under a subpath', async () => {
|
|||
})
|
||||
|
||||
test('can be served without a subpath', async () => {
|
||||
const server = getServer()
|
||||
const server = await getServer()
|
||||
|
||||
await request(server).get('/').expect(200)
|
||||
await request(server).get('/metrics').expect(200)
|
||||
|
|
@ -1,15 +1,16 @@
|
|||
jest.mock('tus-js-client')
|
||||
import { once } from 'node:events'
|
||||
import fs from 'node:fs'
|
||||
import { createServer } from 'node:http'
|
||||
import { Readable } from 'node:stream'
|
||||
import nock from 'nock'
|
||||
import { afterAll, describe, expect, test, vi } from 'vitest'
|
||||
import Emitter from '../src/server/emitter/index.js'
|
||||
import Uploader, { ValidationError } from '../src/server/Uploader.js'
|
||||
import standalone from '../src/standalone/index.js'
|
||||
import * as socketClient from './mocksocket.js'
|
||||
|
||||
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')
|
||||
const socketClient = require('../mocksocket')
|
||||
const standalone = require('../../src/standalone')
|
||||
const Emitter = require('../../src/server/emitter')
|
||||
vi.mock('tus-js-client')
|
||||
vi.mock('express-prom-bundle')
|
||||
|
||||
afterAll(() => {
|
||||
nock.cleanAll()
|
||||
|
|
@ -33,8 +34,9 @@ describe('uploader with tus protocol', () => {
|
|||
},
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
expect(() => new Uploader(opts)).toThrow(
|
||||
new Uploader.ValidationError(
|
||||
new ValidationError(
|
||||
'upload destination does not match any allowed destinations',
|
||||
),
|
||||
)
|
||||
|
|
@ -49,6 +51,7 @@ describe('uploader with tus protocol', () => {
|
|||
},
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
new Uploader(opts) // no validation error
|
||||
})
|
||||
|
||||
|
|
@ -61,6 +64,7 @@ describe('uploader with tus protocol', () => {
|
|||
},
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
new Uploader(opts) // no validation error
|
||||
})
|
||||
|
||||
|
|
@ -75,16 +79,18 @@ describe('uploader with tus protocol', () => {
|
|||
pathPrefix: companionOptions.filePath,
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
const uploader = new Uploader(opts)
|
||||
const uploadToken = uploader.token
|
||||
expect(uploadToken).toBeTruthy()
|
||||
|
||||
let firstReceivedProgress
|
||||
|
||||
const onProgress = jest.fn()
|
||||
const onUploadSuccess = jest.fn()
|
||||
const onBeginUploadEvent = jest.fn()
|
||||
const onUploadEvent = jest.fn()
|
||||
const onProgress = vi.fn()
|
||||
const onUploadSuccess = vi.fn()
|
||||
const onUploadError = vi.fn()
|
||||
const onBeginUploadEvent = vi.fn()
|
||||
const onUploadEvent = vi.fn()
|
||||
|
||||
const emitter = Emitter()
|
||||
emitter.on('upload-start', onBeginUploadEvent)
|
||||
|
|
@ -98,10 +104,14 @@ describe('uploader with tus protocol', () => {
|
|||
firstReceivedProgress = message.payload.bytesUploaded
|
||||
onProgress(message)
|
||||
})
|
||||
socketClient.onUploadError(uploadToken, onUploadError)
|
||||
socketClient.onUploadSuccess(uploadToken, onUploadSuccess)
|
||||
await promise
|
||||
// @ts-ignore
|
||||
await uploader.tryUploadStream(stream, mockReq)
|
||||
|
||||
expect(onUploadError).not.toHaveBeenCalled()
|
||||
|
||||
expect(firstReceivedProgress).toBe(8)
|
||||
|
||||
expect(onProgress).toHaveBeenLastCalledWith(
|
||||
|
|
@ -139,6 +149,7 @@ describe('uploader with tus protocol', () => {
|
|||
pathPrefix: companionOptions.filePath,
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
const uploader = new Uploader(opts)
|
||||
const originalTryDeleteTmpPath = uploader.tryDeleteTmpPath.bind(uploader)
|
||||
uploader.tryDeleteTmpPath = async () => {
|
||||
|
|
@ -156,8 +167,10 @@ describe('uploader with tus protocol', () => {
|
|||
return new Promise((resolve, reject) => {
|
||||
// validate that the test is resolved on socket connection
|
||||
uploader.awaitReady(60000).then(() => {
|
||||
// @ts-ignore
|
||||
uploader.tryUploadStream(stream, mockReq).then(() => {
|
||||
try {
|
||||
// @ts-ignore
|
||||
expect(fs.existsSync(uploader.path)).toBe(false)
|
||||
resolve()
|
||||
} catch (err) {
|
||||
|
|
@ -188,7 +201,9 @@ describe('uploader with tus protocol', () => {
|
|||
})
|
||||
|
||||
async function runMultipartTest({
|
||||
// @ts-ignore
|
||||
metadata,
|
||||
// @ts-ignore
|
||||
useFormData,
|
||||
includeSize = true,
|
||||
address = 'localhost',
|
||||
|
|
@ -207,6 +222,7 @@ describe('uploader with tus protocol', () => {
|
|||
}
|
||||
|
||||
const uploader = new Uploader(opts)
|
||||
// @ts-ignore
|
||||
return uploader.uploadStream(stream)
|
||||
}
|
||||
|
||||
|
|
@ -227,6 +243,7 @@ describe('uploader with tus protocol', () => {
|
|||
await once(server, 'listening')
|
||||
|
||||
const ret = await runMultipartTest({
|
||||
// @ts-ignore
|
||||
address: `localhost:${server.address().port}`,
|
||||
})
|
||||
expect(ret).toMatchObject({
|
||||
|
|
@ -244,6 +261,7 @@ describe('uploader with tus protocol', () => {
|
|||
test('upload functions with xhr formdata', async () => {
|
||||
nock('http://localhost').post('/', formDataNoMetaMatch).reply(200)
|
||||
|
||||
// @ts-ignore
|
||||
const ret = await runMultipartTest({ useFormData: true })
|
||||
expect(ret).toMatchObject({
|
||||
url: null,
|
||||
|
|
@ -255,6 +273,7 @@ describe('uploader with tus protocol', () => {
|
|||
nock('http://localhost').post('/', formDataNoMetaMatch).reply(200)
|
||||
|
||||
const ret = await runMultipartTest({
|
||||
// @ts-ignore
|
||||
useFormData: true,
|
||||
includeSize: false,
|
||||
})
|
||||
|
|
@ -271,6 +290,7 @@ describe('uploader with tus protocol', () => {
|
|||
'/',
|
||||
/^--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="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$/,
|
||||
)
|
||||
.times(10)
|
||||
.reply(200)
|
||||
|
||||
const metadata = {
|
||||
|
|
@ -280,6 +300,7 @@ describe('uploader with tus protocol', () => {
|
|||
key4: {},
|
||||
key5: () => {},
|
||||
}
|
||||
// @ts-ignore
|
||||
const ret = await runMultipartTest({ useFormData: true, metadata })
|
||||
expect(ret).toMatchObject({
|
||||
url: null,
|
||||
|
|
@ -293,10 +314,12 @@ describe('uploader with tus protocol', () => {
|
|||
endpoint: 'http://localhost',
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
new Uploader({ ...opts, metadata: { key: 'string value' } })
|
||||
|
||||
// @ts-ignore
|
||||
expect(() => new Uploader({ ...opts, metadata: '' })).toThrow(
|
||||
new Uploader.ValidationError('metadata must be an object'),
|
||||
new ValidationError('metadata must be an object'),
|
||||
)
|
||||
})
|
||||
|
||||
|
|
@ -307,8 +330,9 @@ describe('uploader with tus protocol', () => {
|
|||
size: 101,
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
expect(() => new Uploader(opts)).toThrow(
|
||||
new Uploader.ValidationError('maxFileSize exceeded'),
|
||||
new ValidationError('maxFileSize exceeded'),
|
||||
)
|
||||
})
|
||||
|
||||
|
|
@ -319,6 +343,7 @@ describe('uploader with tus protocol', () => {
|
|||
size: 99,
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
new Uploader(opts) // no validation error
|
||||
})
|
||||
|
||||
|
|
@ -333,12 +358,14 @@ describe('uploader with tus protocol', () => {
|
|||
pathPrefix: companionOptions.filePath,
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
const uploader = new Uploader(opts)
|
||||
const uploadToken = uploader.token
|
||||
|
||||
// validate that the test is resolved on socket connection
|
||||
uploader
|
||||
.awaitReady(60000)
|
||||
// @ts-ignore
|
||||
.then(() => uploader.tryUploadStream(stream, mockReq))
|
||||
socketClient.connect(uploadToken)
|
||||
|
||||
|
|
@ -1,18 +1,22 @@
|
|||
const nock = require('nock')
|
||||
const request = require('supertest')
|
||||
import nock from 'nock'
|
||||
import request from 'supertest'
|
||||
import { afterAll, beforeAll, describe, expect, test, vi } from 'vitest'
|
||||
|
||||
jest.mock('tus-js-client')
|
||||
jest.mock('../../src/server/helpers/request', () => {
|
||||
import { getServer } from './mockserver.js'
|
||||
|
||||
vi.mock('express-prom-bundle')
|
||||
vi.mock('tus-js-client')
|
||||
vi.mock('../src/server/helpers/request.js', async () => {
|
||||
return {
|
||||
...jest.requireActual('../../src/server/helpers/request'),
|
||||
...(await vi.importActual('../src/server/helpers/request.js')),
|
||||
getURLMeta: () => {
|
||||
return Promise.resolve({ size: 7580, type: 'image/jpg' })
|
||||
},
|
||||
}
|
||||
})
|
||||
const { getServer } = require('../mockserver')
|
||||
|
||||
const mockServer = getServer({ COMPANION_CLIENT_SOCKET_CONNECT_TIMEOUT: '0' })
|
||||
const getMockServer = async () =>
|
||||
getServer({ COMPANION_CLIENT_SOCKET_CONNECT_TIMEOUT: '0' })
|
||||
|
||||
beforeAll(() => {
|
||||
nock('http://url.myendpoint.com')
|
||||
|
|
@ -33,8 +37,8 @@ const invalids = [
|
|||
]
|
||||
|
||||
describe('url meta', () => {
|
||||
test("return a url's meta data", () => {
|
||||
return request(mockServer)
|
||||
test("return a url's meta data", async () => {
|
||||
return request(await getMockServer())
|
||||
.post('/url/meta')
|
||||
.set('Content-Type', 'application/json')
|
||||
.send({
|
||||
|
|
@ -47,8 +51,8 @@ describe('url meta', () => {
|
|||
})
|
||||
})
|
||||
|
||||
test.each(invalids)('return 400 for invalid url', (urlCase) => {
|
||||
return request(mockServer)
|
||||
test.each(invalids)('return 400 for invalid url', async (urlCase) => {
|
||||
return request(await getMockServer())
|
||||
.post('/url/meta')
|
||||
.set('Content-Type', 'application/json')
|
||||
.send({
|
||||
|
|
@ -60,8 +64,8 @@ describe('url meta', () => {
|
|||
})
|
||||
|
||||
describe('url get', () => {
|
||||
test('url download gets instanitated', () => {
|
||||
return request(mockServer)
|
||||
test('url download gets instanitated', async () => {
|
||||
return request(await getMockServer())
|
||||
.post('/url/get')
|
||||
.set('Content-Type', 'application/json')
|
||||
.send({
|
||||
|
|
@ -75,8 +79,8 @@ describe('url get', () => {
|
|||
|
||||
test.each(invalids)(
|
||||
'downloads are not instantiated for invalid urls',
|
||||
(urlCase) => {
|
||||
return request(mockServer)
|
||||
async (urlCase) => {
|
||||
return request(await getMockServer())
|
||||
.post('/url/get')
|
||||
.set('Content-Type', 'application/json')
|
||||
.send({
|
||||
8
packages/@uppy/companion/tsconfig.build.json
Normal file
8
packages/@uppy/companion/tsconfig.build.json
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"extends": "./tsconfig.shared",
|
||||
"compilerOptions": {
|
||||
"outDir": "./lib",
|
||||
"noEmitOnError": true
|
||||
},
|
||||
"include": ["src/**/*"]
|
||||
}
|
||||
|
|
@ -1,16 +1,7 @@
|
|||
{
|
||||
"extends": "./tsconfig.shared",
|
||||
"compilerOptions": {
|
||||
"outDir": "./lib",
|
||||
"module": "Node16",
|
||||
"moduleResolution": "node16",
|
||||
"declaration": true,
|
||||
"target": "es2022",
|
||||
"noImplicitAny": false,
|
||||
"sourceMap": false,
|
||||
"allowJs": true,
|
||||
"checkJs": true,
|
||||
"skipLibCheck": true,
|
||||
"noEmitOnError": true
|
||||
"noEmit": true
|
||||
},
|
||||
"include": ["src/**/*"]
|
||||
"include": ["src/**/*", "test/**/*"]
|
||||
}
|
||||
|
|
|
|||
14
packages/@uppy/companion/tsconfig.shared.json
Normal file
14
packages/@uppy/companion/tsconfig.shared.json
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"module": "NodeNext",
|
||||
"declaration": true,
|
||||
"target": "es2022",
|
||||
"noImplicitAny": false,
|
||||
"sourceMap": false,
|
||||
"allowJs": true,
|
||||
"checkJs": true,
|
||||
"skipLibCheck": true,
|
||||
"resolveJsonModule": true,
|
||||
"types": []
|
||||
}
|
||||
}
|
||||
7
packages/@uppy/companion/vitest.config.ts
Normal file
7
packages/@uppy/companion/vitest.config.ts
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
import { defineConfig } from 'vitest/config'
|
||||
|
||||
export default defineConfig({
|
||||
test: {
|
||||
include: ['{src,test}/**/*.{test,spec}.?(c|m)[jt]s?(x)'],
|
||||
},
|
||||
})
|
||||
|
|
@ -21,19 +21,19 @@ const sampleImage = fs.readFileSync(
|
|||
)
|
||||
|
||||
const file1 = {
|
||||
source: 'jest',
|
||||
source: 'test',
|
||||
name: 'image-1.jpeg',
|
||||
type: 'image/jpeg',
|
||||
data: new File([sampleImage], 'image-1.jpeg', { type: 'image/jpeg' }),
|
||||
}
|
||||
const file2 = {
|
||||
source: 'jest',
|
||||
source: 'test',
|
||||
name: 'yolo',
|
||||
type: 'image/jpeg',
|
||||
data: new File([sampleImage], 'yolo', { type: 'image/jpeg' }),
|
||||
}
|
||||
const file3 = {
|
||||
source: 'jest',
|
||||
source: 'test',
|
||||
name: 'my.file.is.weird.png',
|
||||
type: 'image/png',
|
||||
data: new File([sampleImage], 'my.file.is.weird.png', { type: 'image/png' }),
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue