Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

@uppy/companion-client: refactor to TS #4744

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
'use strict'

class AuthError extends Error {
public isAuthError: true

constructor () {
super('Authorization required')
this.name = 'AuthError'
Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
'use strict'

import RequestClient from './RequestClient.js'
import * as tokenStorage from './tokenStorage.js'
import RequestClient from './RequestClient.ts'
import * as tokenStorage from './tokenStorage.ts'

const getName = (id) => {
const getName = (id: string): string => {
return id.split('-').map((s) => s.charAt(0).toUpperCase() + s.slice(1)).join(' ')
}

function getOrigin () {
function getOrigin (): Location['origin'] {
// eslint-disable-next-line no-restricted-globals
return location.origin
}

function getRegex (value) {
function getRegex (value: unknown): RegExp | undefined {
if (typeof value === 'string') {
return new RegExp(`^${value}$`)
} if (value instanceof RegExp) {
Expand All @@ -21,14 +21,14 @@
return undefined
}

function isOriginAllowed (origin, allowedOrigin) {
function isOriginAllowed (origin: Location['origin'], allowedOrigin: unknown): boolean {
const patterns = Array.isArray(allowedOrigin) ? allowedOrigin.map(getRegex) : [getRegex(allowedOrigin)]
return patterns
.some((pattern) => pattern?.test(origin) || pattern?.test(`${origin}/`)) // allowing for trailing '/'
}

export default class Provider extends RequestClient {
#refreshingTokenPromise
#refreshingTokenPromise: Promise<void>

constructor (uppy, opts) {
super(uppy, opts)
Expand All @@ -41,9 +41,9 @@
this.preAuthToken = null
}

async headers () {
async headers (): Promise<Record<string, string>> {
const [headers, token] = await Promise.all([super.headers(), this.#getAuthToken()])
const authHeaders = {}
const authHeaders: Record<string, string> = { __proto__: null as unknown as string }
if (token) {
authHeaders['uppy-auth-token'] = token
}
Expand All @@ -56,7 +56,7 @@
return { ...headers, ...authHeaders }
}

onReceiveResponse (response) {
onReceiveResponse (response: Response) {

Check failure on line 59 in packages/@uppy/companion-client/src/Provider.ts

View workflow job for this annotation

GitHub Actions / Lint JavaScript/TypeScript

Missing return type on function
super.onReceiveResponse(response)
const plugin = this.uppy.getPlugin(this.pluginId)
const oldAuthenticated = plugin.getPluginState().authenticated
Expand All @@ -65,15 +65,15 @@
return response
}

async setAuthToken (token) {

Check failure on line 68 in packages/@uppy/companion-client/src/Provider.ts

View workflow job for this annotation

GitHub Actions / Lint JavaScript/TypeScript

Missing return type on function
return this.uppy.getPlugin(this.pluginId).storage.setItem(this.tokenKey, token)
}

async #getAuthToken () {

Check failure on line 72 in packages/@uppy/companion-client/src/Provider.ts

View workflow job for this annotation

GitHub Actions / Lint JavaScript/TypeScript

Missing return type on function
return this.uppy.getPlugin(this.pluginId).storage.getItem(this.tokenKey)
}

async #removeAuthToken () {

Check failure on line 76 in packages/@uppy/companion-client/src/Provider.ts

View workflow job for this annotation

GitHub Actions / Lint JavaScript/TypeScript

Missing return type on function
return this.uppy.getPlugin(this.pluginId).storage.removeItem(this.tokenKey)
}

Expand All @@ -81,7 +81,7 @@
* Ensure we have a preauth token if necessary. Attempts to fetch one if we don't,
* or rejects if loading one fails.
*/
async ensurePreAuth () {

Check failure on line 84 in packages/@uppy/companion-client/src/Provider.ts

View workflow job for this annotation

GitHub Actions / Lint JavaScript/TypeScript

Missing return type on function
if (this.companionKeysParams && !this.preAuthToken) {
await this.fetchPreAuthToken()

Expand All @@ -91,7 +91,7 @@
}
}

authUrl (queries = {}) {

Check failure on line 94 in packages/@uppy/companion-client/src/Provider.ts

View workflow job for this annotation

GitHub Actions / Lint JavaScript/TypeScript

Missing return type on function
const params = new URLSearchParams({
state: btoa(JSON.stringify({ origin: getOrigin() })),
...queries,
Expand All @@ -103,13 +103,13 @@
return `${this.hostname}/${this.id}/connect?${params}`
}

async login (queries) {

Check failure on line 106 in packages/@uppy/companion-client/src/Provider.ts

View workflow job for this annotation

GitHub Actions / Lint JavaScript/TypeScript

Missing return type on function
await this.ensurePreAuth()

return new Promise((resolve, reject) => {
const link = this.authUrl(queries)
const authWindow = window.open(link, '_blank')
const handleToken = (e) => {

Check failure on line 112 in packages/@uppy/companion-client/src/Provider.ts

View workflow job for this annotation

GitHub Actions / Lint JavaScript/TypeScript

Missing return type on function
if (e.source !== authWindow) {
this.uppy.log.warn('ignoring event from unknown source', e)
return
Expand Down Expand Up @@ -146,11 +146,11 @@
})
}

refreshTokenUrl () {

Check failure on line 149 in packages/@uppy/companion-client/src/Provider.ts

View workflow job for this annotation

GitHub Actions / Lint JavaScript/TypeScript

Missing return type on function
return `${this.hostname}/${this.id}/refresh-token`
}

fileUrl (id) {

Check failure on line 153 in packages/@uppy/companion-client/src/Provider.ts

View workflow job for this annotation

GitHub Actions / Lint JavaScript/TypeScript

Missing return type on function
return `${this.hostname}/${this.id}/get/${id}`
}

Expand All @@ -158,7 +158,7 @@
async request (...args) {
await this.#refreshingTokenPromise

try {
try{
// to test simulate access token expired (leading to a token token refresh),
// see mockAccessTokenExpiredError in companion/drive.
// If you want to test refresh token *and* access token invalid, do this for example with Google Drive:
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { describe, it, expect } from 'vitest'
import RequestClient from './RequestClient.js'
import RequestClient from './RequestClient.ts'

describe('RequestClient', () => {
it('has a hostname without trailing slash', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,15 @@
import emitSocketProgress from '@uppy/utils/lib/emitSocketProgress'
import getSocketHost from '@uppy/utils/lib/getSocketHost'

import AuthError from './AuthError.js'

import AuthError from './AuthError.ts'
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore We don't want TS to generate types for the package.json
import packageJson from '../package.json'

// Remove the trailing slash so we can always safely append /xyz.
function stripSlash (url) {
/**
* Removes the trailing slash so we can always safely append /xyz.
*/
function stripSlash (url: string): string {
return url.replace(/\/$/, '')
}

Expand All @@ -23,15 +26,15 @@
const authErrorStatusCode = 401

class HttpError extends Error {
statusCode
public statusCode: number

constructor({ statusCode, message }) {
constructor({ statusCode, message }: { statusCode: number, message: string}) {
super(message)
this.statusCode = statusCode
}
}

async function handleJSONResponse (res) {
async function handleJSONResponse (res: Response): Promise<ReturnType<typeof JSON.parse>> {
if (res.status === authErrorStatusCode) {
throw new AuthError()
}
Expand Down Expand Up @@ -61,30 +64,30 @@
export default class RequestClient {
static VERSION = packageJson.version

#companionHeaders
#companionHeaders: Record<string, string>

constructor (uppy, opts) {

Check failure on line 69 in packages/@uppy/companion-client/src/RequestClient.ts

View workflow job for this annotation

GitHub Actions / Type tests

Parameter 'uppy' implicitly has an 'any' type.

Check failure on line 69 in packages/@uppy/companion-client/src/RequestClient.ts

View workflow job for this annotation

GitHub Actions / Type tests

Parameter 'opts' implicitly has an 'any' type.
this.uppy = uppy

Check failure on line 70 in packages/@uppy/companion-client/src/RequestClient.ts

View workflow job for this annotation

GitHub Actions / Type tests

Property 'uppy' does not exist on type 'RequestClient'.
this.opts = opts

Check failure on line 71 in packages/@uppy/companion-client/src/RequestClient.ts

View workflow job for this annotation

GitHub Actions / Type tests

Property 'opts' does not exist on type 'RequestClient'.
this.onReceiveResponse = this.onReceiveResponse.bind(this)
this.#companionHeaders = opts?.companionHeaders
}

setCompanionHeaders (headers) {
setCompanionHeaders (headers: Record<string, string>): void {
this.#companionHeaders = headers
}

[Symbol.for('uppy test: getCompanionHeaders')] () {
private [Symbol.for('uppy test: getCompanionHeaders')] (): Record<string, string> {
return this.#companionHeaders
}

get hostname () {
get hostname (): string {
const { companion } = this.uppy.getState()

Check failure on line 85 in packages/@uppy/companion-client/src/RequestClient.ts

View workflow job for this annotation

GitHub Actions / Type tests

Property 'uppy' does not exist on type 'RequestClient'.
const host = this.opts.companionUrl

Check failure on line 86 in packages/@uppy/companion-client/src/RequestClient.ts

View workflow job for this annotation

GitHub Actions / Type tests

Property 'opts' does not exist on type 'RequestClient'.
return stripSlash(companion && companion[host] ? companion[host] : host)
}

async headers () {
async headers (): Promise<Record<string, string>> {
const defaultHeaders = {
Accept: 'application/json',
'Content-Type': 'application/json',
Expand All @@ -97,10 +100,10 @@
}
}

onReceiveResponse ({ headers }) {
onReceiveResponse ({ headers }: Response): void {
const state = this.uppy.getState()

Check failure on line 104 in packages/@uppy/companion-client/src/RequestClient.ts

View workflow job for this annotation

GitHub Actions / Type tests

Property 'uppy' does not exist on type 'RequestClient'.
const companion = state.companion || {}
const host = this.opts.companionUrl

Check failure on line 106 in packages/@uppy/companion-client/src/RequestClient.ts

View workflow job for this annotation

GitHub Actions / Type tests

Property 'opts' does not exist on type 'RequestClient'.

// Store the self-identified domain name for the Companion instance we just hit.
if (headers.has('i-am') && headers.get('i-am') !== companion[host]) {
Expand All @@ -110,7 +113,7 @@
}
}

#getUrl (url) {
#getUrl (url: string): string {
if (/^(https?:|)\/\//.test(url)) {
return url
}
Expand All @@ -131,7 +134,7 @@
Subsequent requests use the cached result of the preflight.
However if there is an error retrieving the allowed headers, we will try again next time
*/
async preflight (path) {
async preflight (path: string): Promise<string[]>{
const allowedHeadersCached = allowedHeadersCache.get(this.hostname)
if (allowedHeadersCached != null) return allowedHeadersCached

Expand All @@ -141,7 +144,7 @@
'uppy-auth-token',
]

const promise = (async () => {
const promise = (async (): Promise<string[]> => {
try {
const response = await fetch(this.#getUrl(path), { method: 'OPTIONS' })

Expand Down Expand Up @@ -176,7 +179,7 @@
return promise
}

async preflightAndHeaders (path) {
async preflightAndHeaders (path:string):Record<string,string> {
const [allowedHeaders, headers] = await Promise.all([
this.preflight(path),
this.headers(),
Expand All @@ -195,8 +198,7 @@
)
}

/** @protected */
async request ({ path, method = 'GET', data, skipPostResponse, signal }) {
protected async request ({ path, method = 'GET', data, skipPostResponse, signal }): ReturnType<typeof handleJSONResponse> {
try {
const headers = await this.preflightAndHeaders(path)
const response = await fetchWithNetworkError(this.#getUrl(path), {
Expand All @@ -219,21 +221,21 @@
}
}

async get (path, options = undefined) {
async get (path, options = undefined): ReturnType<typeof handleJSONResponse> {
// TODO: remove boolean support for options that was added for backward compatibility.
// eslint-disable-next-line no-param-reassign
if (typeof options === 'boolean') options = { skipPostResponse: options }
return this.request({ ...options, path })
}

async post (path, data, options = undefined) {
async post (path, data, options = undefined): ReturnType<typeof handleJSONResponse> {
// TODO: remove boolean support for options that was added for backward compatibility.
// eslint-disable-next-line no-param-reassign
if (typeof options === 'boolean') options = { skipPostResponse: options }
return this.request({ ...options, path, method: 'POST', data })
}

async delete (path, data = undefined, options) {
async delete (path, data = undefined, options): ReturnType<typeof handleJSONResponse> {
// TODO: remove boolean support for options that was added for backward compatibility.
// eslint-disable-next-line no-param-reassign
if (typeof options === 'boolean') options = { skipPostResponse: options }
Expand Down Expand Up @@ -312,7 +314,7 @@
}
}

#requestSocketToken = async ({ file, postBody, signal }) => {
#requestSocketToken = async ({ file, postBody, signal }): Promise<string> => {
if (file.remote.url == null) {
throw new Error('Cannot connect to an undefined URL')
}
Expand All @@ -329,10 +331,8 @@
* This method will ensure a websocket for the specified file and returns a promise that resolves
* when the file has finished downloading, or rejects if it fails.
* It will retry if the websocket gets disconnected
*
* @param {{ file: UppyFile, queue: RateLimitedQueue, signal: AbortSignal }} file
*/
async #awaitRemoteFileUpload ({ file, queue, signal }) {
async #awaitRemoteFileUpload ({ file, queue, signal }: { file: UppyFile, queue: RateLimitedQueue, signal?: AbortSignal }): Promise<void> {
let removeEventHandlers

const { capabilities } = this.uppy.getState()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
'use strict'

import RequestClient from './RequestClient.js'
import RequestClient from './RequestClient.ts'

const getName = (id) => {
const getName = (id: string) : string=> {
return id.split('-').map((s) => s.charAt(0).toUpperCase() + s.slice(1)).join(' ')
}

export default class SearchProvider extends RequestClient {
public id: string

constructor (uppy, opts) {
super(uppy, opts)
this.provider = opts.provider
Expand All @@ -15,11 +17,11 @@ export default class SearchProvider extends RequestClient {
this.pluginId = this.opts.pluginId
}

fileUrl (id) {
fileUrl (id: string): string {
return `${this.hostname}/search/${this.id}/get/${id}`
}

search (text, queries) {
search (text: string, queries?: string): Promise<unknown[]> {
return this.get(`search/${this.id}/list?q=${encodeURIComponent(text)}${queries ? `&${queries}` : ''}`)
}
}
9 changes: 0 additions & 9 deletions packages/@uppy/companion-client/src/index.js

This file was deleted.

7 changes: 7 additions & 0 deletions packages/@uppy/companion-client/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
/**
* Manages communications with Companion
*/

export { default as RequestClient } from './RequestClient.ts'
export { default as Provider } from './Provider.ts'
export { default as SearchProvider } from './SearchProvider.ts'
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,18 @@
/**
* This module serves as an Async wrapper for LocalStorage
*/
export function setItem (key, value) {
export function setItem (key: Parameters<Storage['setItem']>[0], value:Parameters<Storage['setItem']>[1]): Promise<void> {
return new Promise((resolve) => {
localStorage.setItem(key, value)
resolve()
})
}

export function getItem (key) {
export function getItem (key: Parameters<Storage['getItem']>[0]): Promise<ReturnType<Storage['getItem']>> {
return Promise.resolve(localStorage.getItem(key))
}

export function removeItem (key) {
export function removeItem (key: Parameters<Storage['removeItem']>[0]): Promise<void> {
return new Promise((resolve) => {
localStorage.removeItem(key)
resolve()
Expand Down
21 changes: 21 additions & 0 deletions packages/@uppy/companion-client/tsconfig.build.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"extends": "../../../tsconfig.shared",
"compilerOptions": {
"outDir": "./lib",
"rootDir": "./src",
"resolveJsonModule": false,
"noImplicitAny": false,
"skipLibCheck": true
},
"include": [
"./src/**/*.*"
],
"exclude": [
"./src/**/*.test.ts"
],
"references": [
{
"path": "../utils/tsconfig.build.json"
}
]
}
16 changes: 16 additions & 0 deletions packages/@uppy/companion-client/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"extends": "../../../tsconfig.shared",
"compilerOptions": {
"emitDeclarationOnly": false,
"noEmit": true
},
"include": [
"./package.json",
"./src/**/*.*"
],
"references": [
{
"path": "../utils/tsconfig.build.json"
}
]
}
Loading