diff --git a/CHANGELOG.md b/CHANGELOG.md index ad40456c4..139cefa46 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,7 +14,7 @@ In the current stage we aim to release a new version at least every month. ### next -## July 2021 +## August 2021 - [ ] robodog: finishing touches on Companion dynamic Oauth #2802 (@goto-bus-stop) - [ ] unsplash: Unsplash re-design (#2635 / @arturi, @nqst) @@ -22,6 +22,79 @@ In the current stage we aim to release a new version at least every month. - [ ] plugin: audio/memo recording similar to Webcam #143 #198 (@arturi) - [ ] compressor: add to Uppy repo, add resizing (@arturi) +## Companion Patch 2.12.2 + +| Package | Version | +|-|-| +| @uppy/companion@2.12.2 | 2.12.2 | + +- @uppy/companion: Improve logging (#3103 / @mifi) + +## 2.0.0-alpha.0 + +| Package | Version | Package | Version | +|-|-|-|-| +| @uppy/angular | 0.2.0-alpha.0 | @uppy/provider-views | 2.0.0-alpha.0 | +| @uppy/aws-s3-multipart | 2.0.0-alpha.0 | @uppy/react-native | 0.2.0-alpha.0 | +| @uppy/aws-s3 | 2.0.0-alpha.0 | @uppy/react | 2.0.0-alpha.0 | +| @uppy/box | 0.4.0-alpha.0 | @uppy/redux-dev-tools | 2.0.0-alpha.0 | +| @uppy/companion-client | 2.0.0-alpha.0 | @uppy/robodog | 2.0.0-alpha.0 | +| @uppy/companion | 2.13.0-alpha.0 | @uppy/screen-capture | 2.0.0-alpha.0 | +| @uppy/core | 2.0.0-alpha.0 | @uppy/status-bar | 2.0.0-alpha.0 | +| @uppy/dashboard | 2.0.0-alpha.0 | @uppy/store-default | 2.0.0-alpha.0 | +| @uppy/drag-drop | 2.0.0-alpha.0 | @uppy/store-redux | 2.0.0-alpha.0 | +| @uppy/drop-target | 1.0.0-alpha.0 | @uppy/svelte | 1.0.0-alpha.0 | +| @uppy/dropbox | 2.0.0-alpha.0 | @uppy/thumbnail-generator | 2.0.0-alpha.0 | +| @uppy/facebook | 2.0.0-alpha.0 | @uppy/transloadit | 2.0.0-alpha.0 | +| @uppy/file-input | 2.0.0-alpha.0 | @uppy/tus | 2.0.0-alpha.0 | +| @uppy/form | 2.0.0-alpha.0 | @uppy/unsplash | 0.2.0-alpha.0 | +| @uppy/golden-retriever | 2.0.0-alpha.0 | @uppy/url | 2.0.0-alpha.0 | +| @uppy/google-drive | 2.0.0-alpha.0 | @uppy/utils | 4.0.0-alpha.0 | +| @uppy/image-editor | 1.0.0-alpha.0 | @uppy/vue | 0.3.0-alpha.0 | +| @uppy/informer | 2.0.0-alpha.0 | @uppy/webcam | 2.0.0-alpha.0 | +| @uppy/instagram | 2.0.0-alpha.0 | @uppy/xhr-upload | 2.0.0-alpha.0 | +| @uppy/locales | 2.0.0-alpha.0 | @uppy/zoom | 0.2.0-alpha.0 | +| @uppy/onedrive | 2.0.0-alpha.0 | remark-lint-uppy | 0.0.2 | +| @uppy/progress-bar | 2.0.0-alpha.0 | uppy | 2.0.0-alpha.0 | + +## 1.31.0 + +Released: 2021-07-29 + +| Package | Version | Package | Version | +|-|-|-|-| +| @uppy/angular | 0.1.3 | @uppy/react | 1.12.1 | +| @uppy/aws-s3 | 1.8.0 | @uppy/robodog | 1.11.0 | +| @uppy/companion | 2.12.0 | @uppy/screen-capture | 1.1.0 | +| @uppy/core | 1.20.0 | @uppy/svelte | 0.1.13 | +| @uppy/dashboard | 1.21.0 | @uppy/transloadit | 1.7.0 | +| @uppy/drag-drop | 1.4.31 | @uppy/vue | 0.2.6 | +| @uppy/image-editor | 0.4.0 | @uppy/webcam | 1.8.13 | +| @uppy/locales | 1.22.0 | uppy | 1.31.0 | + +- @uppy/companion: Fix invalid referrer crashing the process (a785f7deebe5ad75bb2e7ea0874198784c19fea1 / @juliangruber) +- @uppy/companion: Fix typescript error (6dbaddc09d36308821b842ed13a847f5d655cbf4 / @juliangruber) +- @uppy/angular: Fix broken packaging (#3007 / @ajkachnic) +- @uppy/robodog: Add Robodog Types (#2989 / @Hawxy) +- @uppy/core: Tighten duck type check for file objects (#3006 / @goto-bus-stop) +- @uppy/core: tighten duck type check for file objects (#3006 / @goto-bus-stop) +- @uppy/core: Set file size from progress data when null (#2778 / @mejiaej) +- @uppy/core: Mark state as deprecated (#3044 / @aduh95) +- @uppy/locales: Update de_DE.js (#3012 / @paescuj) +- @uppy/dashboard: Rename Done to Cancel, add Save to Image Editor (#3033 / @arturi) +- @uppy/box: Add Box (#3004 / @mifi) +- @uppy/dashboard: Add required option to metaFields (#2896 / @aduh95) +- build: Fix package.json imports to be inlined by Babel (#3047 / @aduh95) +- docs: Add instagram development notes (#2984 / @mifi) +- docs: Update CONTRIBUTING.md (#3011 / @aduh95) +- website: fix linter errors in JS code snippets inside blog posts (#2991 / @aduh95) + +### Patch release + +| Package | Version | Package | Version | +|-|-|-|-| +| @uppy/angular | 0.1.2 | @uppy/companion | 2.11.1 | + ## June 2021 ## 1.30.0 diff --git a/packages/@uppy/companion/src/server/logger.js b/packages/@uppy/companion/src/server/logger.js index c5c036457..68d5563e9 100644 --- a/packages/@uppy/companion/src/server/logger.js +++ b/packages/@uppy/companion/src/server/logger.js @@ -1,5 +1,6 @@ const chalk = require('chalk') const escapeStringRegexp = require('escape-string-regexp') +const util = require('util') const valuesToMask = [] /** @@ -16,6 +17,57 @@ exports.setMaskables = (maskables) => { Object.freeze(valuesToMask) } +/** + * Mask the secret content of a message + * + * @param {string} msg the message whose content should be masked + * @returns {string} + */ +function maskMessage (msg) { + let out = msg + for (const toBeMasked of valuesToMask) { + const toBeReplaced = new RegExp(toBeMasked, 'gi') + out = out.replace(toBeReplaced, '******') + } + return out +} + +/** + * message log + * + * @param {string | Error} msg the message to log + * @param {string} tag a unique tag to easily search for this message + * @param {string} level error | info | debug + * @param {string=} id a unique id to easily trace logs tied to a request + * @param {Function=} color function to display the log in appropriate color + * @param {boolean=} shouldLogStackTrace when set to true, errors will be logged with their stack trace + */ +const log = (msg, tag = '', level, id = '', color = (message) => message, shouldLogStackTrace) => { + const time = new Date().toISOString() + const whitespace = tag && id ? ' ' : '' + + function logMsg (msg2) { + let msgString = typeof msg2 === 'string' ? msg2 : util.inspect(msg2) + msgString = maskMessage(msgString) + // eslint-disable-next-line no-console + console.log(color(`companion: ${time} [${level}] ${id}${whitespace}${tag}`), color(msgString)) + } + + if (msg instanceof Error) { + // Not sure why it only logs the stack without the message, but this is how the code was originally + if (shouldLogStackTrace && typeof msg.stack === 'string') { + logMsg(msg.stack) + return + } + + // We don't want to log stack trace (this is how the code was originally) + logMsg(String(msg)) + return + } + + logMsg(msg) +} + /** * INFO level log * @@ -66,52 +118,3 @@ exports.debug = (msg, tag, traceId) => { log(msg, tag, 'debug', traceId, chalk.bold.blue) } } - -/** - * message log - * - * @param {string | Error} msg the message to log - * @param {string} tag a unique tag to easily search for this message - * @param {string} level error | info | debug - * @param {Function=} color function to display the log in appropriate color - * @param {string=} id a unique id to easily trace logs tied to a request - * @param {boolean=} shouldLogStackTrace when set to true, errors will be logged with their stack trace - */ -const log = (msg, tag, level, id, color, shouldLogStackTrace) => { - const time = new Date().toISOString() - tag = tag || '' - id = id || '' - const whitespace = tag && id ? ' ' : '' - color = color || ((message) => message) - if (typeof msg === 'string') { - msg = maskMessage(msg) - } else if (msg && typeof msg.message === 'string') { - msg.message = maskMessage(msg.message) - } - - if (shouldLogStackTrace && msg instanceof Error && typeof msg.stack === 'string') { - msg.stack = maskMessage(msg.stack) - // exclude msg from template string so values such as error objects - // can be well formatted - console.log(color(`companion: ${time} [${level}] ${id}${whitespace}${tag}`), color(msg.stack)) - return - } - - // exclude msg from template string so values such as error objects - // can be well formatted - console.log(color(`companion: ${time} [${level}] ${id}${whitespace}${tag}`), color(msg)) -} - -/** - * Mask the secret content of a message - * - * @param {string} msg the message whose content should be masked - * @returns {string} - */ -const maskMessage = (msg) => { - for (const toBeMasked of valuesToMask) { - const toBeReplaced = new RegExp(toBeMasked, 'gi') - msg = msg.replace(toBeReplaced, '******') - } - return msg -} diff --git a/packages/@uppy/companion/test/__tests__/logger.js b/packages/@uppy/companion/test/__tests__/logger.js index 3ed7429ff..cdeb0d8f5 100644 --- a/packages/@uppy/companion/test/__tests__/logger.js +++ b/packages/@uppy/companion/test/__tests__/logger.js @@ -2,24 +2,36 @@ const chalk = require('chalk') const logger = require('../../src/server/logger') -describe('Test Logger secret mask', () => { - beforeAll(() => { - logger.setMaskables(['ToBeMasked1', 'toBeMasked2', 'toBeMasked(And)?Escaped']) - }) +const maskables = ['ToBeMasked1', 'toBeMasked2', 'toBeMasked(And)?Escaped'] - test('masks secret values present in log.info messages', () => { - let loggedMessage = null +function captureConsoleLog (log) { + let loggedMessage = null - // override the default console.log to capture the logged message - const defaultConsoleLog = console.log + // override the default console.log to capture the logged message + const defaultConsoleLog = console.log + + try { console.log = (logPrefix, message) => { loggedMessage = message defaultConsoleLog(logPrefix, message) } - - logger.info('this info has ToBeMasked1 and toBeMasked2 and case-insensitive TOBEMasKED2') + } finally { + log() // restore the default console.log before using "expect" to avoid weird log behaviors console.log = defaultConsoleLog + } + return loggedMessage +} + +describe('Test Logger secret mask', () => { + beforeAll(() => { + logger.setMaskables(maskables) + }) + + test('masks secret values present in log.info messages', () => { + const loggedMessage = captureConsoleLog(() => { + logger.info('this info has ToBeMasked1 and toBeMasked2 and case-insensitive TOBEMasKED2') + }) const exptectedMsg = 'this info has ****** and ****** and case-insensitive ******' @@ -28,18 +40,9 @@ describe('Test Logger secret mask', () => { }) test('masks secret values present in log.warn messages', () => { - let loggedMessage = null - - // override the default console.log to capture the logged message - const defaultConsoleLog = console.log - console.log = (logPrefix, message) => { - loggedMessage = message - defaultConsoleLog(logPrefix, message) - } - - logger.warn('this warning has ToBeMasked1 and toBeMasked2 and case-insensitive TOBEMasKED2') - // restore the default console.log before using "expect" to avoid weird log behaviors - console.log = defaultConsoleLog + const loggedMessage = captureConsoleLog(() => { + logger.warn('this warning has ToBeMasked1 and toBeMasked2 and case-insensitive TOBEMasKED2') + }) const exptectedMsg = chalk.bold.yellow('this warning has ****** and ****** and case-insensitive ******') @@ -48,18 +51,9 @@ describe('Test Logger secret mask', () => { }) test('masks secret values present in log.error messages', () => { - let loggedMessage = null - - // override the default console.log to capture the logged message - const defaultConsoleLog = console.log - console.log = (logPrefix, message) => { - loggedMessage = message - defaultConsoleLog(logPrefix, message) - } - - logger.error(new Error('this error has ToBeMasked1 and toBeMasked2 and case-insensitive TOBEMasKED2')) - // restore the default console.log before using "expect" to avoid weird log behaviors - console.log = defaultConsoleLog + const loggedMessage = captureConsoleLog(() => { + logger.error(new Error('this error has ToBeMasked1 and toBeMasked2 and case-insensitive TOBEMasKED2')) + }) const exptectedMsg = chalk.bold.red('Error: this error has ****** and ****** and case-insensitive ******') @@ -68,19 +62,10 @@ describe('Test Logger secret mask', () => { }) test('masks secret values present in log.error stack trace', () => { - let loggedMessage = null - - // override the default console.log to capture the logged message - const defaultConsoleLog = console.log - console.log = (logPrefix, message) => { - loggedMessage = message - defaultConsoleLog(logPrefix, message) - } - - const err = new Error('this error has ToBeMasked1 and toBeMasked2 and case-insensitive TOBEMasKED2') - logger.error(err, '', '', true) - // restore the default console.log before using "expect" to avoid weird log behaviors - console.log = defaultConsoleLog + const loggedMessage = captureConsoleLog(() => { + const err = new Error('this error has ToBeMasked1 and toBeMasked2 and case-insensitive TOBEMasKED2') + logger.error(err, '', '', true) + }) const exptectedMsg = chalk.bold.red('Error: this error has ****** and ****** and case-insensitive ******') @@ -92,22 +77,22 @@ describe('Test Logger secret mask', () => { }) test('escape regex characters from secret values before masking them', () => { - let loggedMessage = null - - // override the default console.log to capture the logged message - const defaultConsoleLog = console.log - console.log = (logPrefix, message) => { - loggedMessage = message - defaultConsoleLog(logPrefix, message) - } - - logger.warn('this warning has ToBeMasked(And)?Escaped but not toBeMaskedEscaped ') - // restore the default console.log before using "expect" to avoid weird log behaviors - console.log = defaultConsoleLog + const loggedMessage = captureConsoleLog(() => { + logger.warn('this warning has ToBeMasked(And)?Escaped but not toBeMaskedEscaped ') + }) const exptectedMsg = chalk.bold.yellow('this warning has ****** but not toBeMaskedEscaped ') expect(loggedMessage).toBeTruthy() expect(loggedMessage).toBe(exptectedMsg) }) + + test('masks inside object', () => { + const loggedMessage = captureConsoleLog(() => { + logger.warn({ a: 1, deep: { secret: 'there is a ToBeMasked1 hiding here' } }) + }) + + expect(loggedMessage).toBeTruthy() + expect(!maskables.some((maskable) => loggedMessage.includes(maskable))).toBeTruthy() + }) }) diff --git a/packages/@uppy/core/src/BasePlugin.js b/packages/@uppy/core/src/BasePlugin.js index 534efefa7..d03146e4c 100644 --- a/packages/@uppy/core/src/BasePlugin.js +++ b/packages/@uppy/core/src/BasePlugin.js @@ -75,9 +75,6 @@ module.exports = class BasePlugin { throw new Error('Extend the render method to add your plugin to a DOM element') } - // eslint-disable-next-line class-methods-use-this - onMount () {} - // eslint-disable-next-line class-methods-use-this update () {} diff --git a/packages/@uppy/core/src/UIPlugin.js b/packages/@uppy/core/src/UIPlugin.js index 0cf0f1a41..cb6de56f5 100644 --- a/packages/@uppy/core/src/UIPlugin.js +++ b/packages/@uppy/core/src/UIPlugin.js @@ -134,7 +134,14 @@ class UIPlugin extends BasePlugin { if (this.isTargetDOMEl) { this.el?.remove() } + this.onUnmount() } + + // eslint-disable-next-line class-methods-use-this + onMount () {} + + // eslint-disable-next-line class-methods-use-this + onUnmount () {} } module.exports = UIPlugin diff --git a/packages/@uppy/core/types/index.d.ts b/packages/@uppy/core/types/index.d.ts index 676b23238..802c1e0e0 100644 --- a/packages/@uppy/core/types/index.d.ts +++ b/packages/@uppy/core/types/index.d.ts @@ -14,7 +14,9 @@ export interface IndexedObject { export type UppyFile< TMeta extends IndexedObject = Record, TBody extends IndexedObject = Record -> = UppyUtils.UppyFile + > = UppyUtils.UppyFile + +export type FileProgress = UppyUtils.FileProgress; // Replace the `meta` property type with one that allows omitting internal metadata addFile() will add that type UppyFileWithoutMeta = OmitKey< @@ -28,28 +30,6 @@ type LocaleStrings = { type LogLevel = 'info' | 'warning' | 'error' -// This hack accepts _any_ string for `Event`, but also tricks VSCode and friends into providing autocompletions -// for the names listed. https://github.com/microsoft/TypeScript/issues/29729#issuecomment-505826972 -// eslint-disable-next-line no-use-before-define -type LiteralUnion = T | (U & Record) - -type Event = LiteralUnion< - | 'file-added' - | 'file-removed' - | 'upload' - | 'upload-progress' - | 'upload-success' - | 'complete' - | 'error' - | 'upload-error' - | 'upload-retry' - | 'info-visible' - | 'info-hidden' - | 'cancel-all' - | 'restriction-failed' - | 'reset-progress' -> - export type Store = UppyUtils.Store export type InternalMetadata = UppyUtils.InternalMetadata @@ -65,7 +45,7 @@ export interface FailedUppyFile extends UppyFile { export interface AddFileOptions< TMeta = IndexedObject, TBody = IndexedObject -> extends Partial> { + > extends Partial> { // `.data` is the only required property here. data: Blob | File meta?: Partial & TMeta @@ -122,6 +102,10 @@ export class UIPlugin ext addTarget(plugin: TPlugin): void unmount(): void + + onMount(): void + + onUnmount(): void } export type PluginTarget = @@ -173,7 +157,7 @@ export interface UppyOptions = Record = Record, TBody extends IndexedObject = Record -> { + > { successful: UploadedUppyFile[] failed: FailedUppyFile[] } @@ -181,14 +165,14 @@ export interface UploadResult< export interface State< TMeta extends IndexedObject = Record, TBody extends IndexedObject = Record -> extends IndexedObject { + > extends IndexedObject { capabilities?: { resumableUploads?: boolean } currentUploads: Record error?: string files: { [key: string]: - | UploadedUppyFile - | FailedUppyFile + | UploadedUppyFile + | FailedUppyFile } info?: { isHidden: boolean @@ -200,32 +184,56 @@ export interface State< totalProgress: number } -type UploadSuccessCallback = (file: UppyFile, body: any, uploadURL: string) => void -type UploadCompleteCallback = (result: UploadResult) => void +export type GenericEventCallback = () => void; +export type FileAddedCallback = (file: UppyFile) => void; +export type FilesAddedCallback = (files: UppyFile[]) => void; +export type FileRemovedCallback = (file: UppyFile, reason: 'removed-by-user' | 'cancel-all') => void; +export type UploadCallback = (data: {id: string, fileIDs: string[]}) => void; +export type ProgressCallback = (progress: number) => void; +export type UploadProgressCallback = (file: UppyFile, progress: FileProgress) => void; +export type UploadSuccessCallback = (file: UploadedUppyFile, body: unknown, uploadURL: string) => void +export type UploadCompleteCallback = (result: UploadResult) => void +export type ErrorCallback = (error: Error) => void; +export type UploadErrorCallback = (file: FailedUppyFile, error: Error, response: unknown) => void; +export type UploadRetryCallback = (fileID: string) => void; +export type RestrictionFailedCallback = (file: UppyFile, error: Error) => void; + +export interface UppyEventMap> { + 'file-added': FileAddedCallback + 'files-added': FilesAddedCallback + 'file-removed': FileRemovedCallback + 'upload': UploadCallback + 'progress': ProgressCallback + 'upload-progress': UploadProgressCallback + 'upload-success': UploadSuccessCallback + 'complete': UploadCompleteCallback + 'error': ErrorCallback + 'upload-error': UploadErrorCallback + 'upload-retry': UploadRetryCallback + 'info-visible': GenericEventCallback + 'info-hidden': GenericEventCallback + 'cancel-all': GenericEventCallback + 'restriction-failed': RestrictionFailedCallback + 'reset-progress': GenericEventCallback +} export class Uppy { constructor(opts?: UppyOptions) - on = Record>(event: 'upload-success', callback: UploadSuccessCallback): this + on(event: K, callback: UppyEventMap[K]): this - on = Record>(event: 'complete', callback: UploadCompleteCallback): this + on>(event: K, callback: UppyEventMap[K]): this - on(event: Event, callback: (...args: any[]) => void): this + once(event: K, callback: UppyEventMap[K]): this - once = Record>(event: 'upload-success', callback: UploadSuccessCallback): this + once>(event: K, callback: UppyEventMap[K]): this - once = Record>(event: 'complete', callback: UploadCompleteCallback): this - - once(event: Event, callback: (...args: any[]) => void): this - - off(event: Event, callback: (...args: any[]) => void): this - - off(event: Event, callback: (...args: any[]) => void): this + off(event: K, callback: UppyEventMap[K]): this /** * For use by plugins only. */ - emit(event: Event, ...args: any[]): void + emit(event: string, ...args: any[]): void updateAll(state: Record): void diff --git a/packages/@uppy/core/types/index.test-d.ts b/packages/@uppy/core/types/index.test-d.ts index cf2b1d062..f951c1daa 100644 --- a/packages/@uppy/core/types/index.test-d.ts +++ b/packages/@uppy/core/types/index.test-d.ts @@ -87,11 +87,18 @@ type anyObject = Record uppy.once('upload', () => {}) uppy.once('complete', () => {}) uppy.once('error', () => {}) - - // can register listeners on custom events - uppy.on('dashboard:modal-closed', () => {}) - uppy.once('dashboard:modal-closed', () => {}) /* eslint-enable @typescript-eslint/no-empty-function */ + + // Normal event signature + uppy.on('complete', (result) => { + const successResults = result.successful + }) + + // Meta signature + type Meta = {myCustomMetadata: string} + uppy.on<'complete', Meta>('complete', (result) => { + const meta = result.successful[0].meta.myCustomMetadata + }) } { diff --git a/packages/@uppy/dashboard/src/components/FileItem/Buttons/index.js b/packages/@uppy/dashboard/src/components/FileItem/Buttons/index.js index f7ec66c2d..b560c7816 100644 --- a/packages/@uppy/dashboard/src/components/FileItem/Buttons/index.js +++ b/packages/@uppy/dashboard/src/components/FileItem/Buttons/index.js @@ -17,8 +17,8 @@ function EditButton ({