focalboard/webapp/src/octoClient.ts

353 lines
12 KiB
TypeScript
Raw Normal View History

2020-10-20 21:50:53 +02:00
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
2020-10-22 18:46:06 +02:00
import {IBlock, IMutableBlock} from './blocks/block'
2021-01-13 01:52:25 +01:00
import {ISharing} from './blocks/sharing'
2021-01-14 01:56:01 +01:00
import {IWorkspace} from './blocks/workspace'
2020-12-07 20:40:16 +01:00
import {IUser} from './user'
2020-10-20 21:52:56 +02:00
import {Utils} from './utils'
2020-10-08 18:21:27 +02:00
//
// OctoClient is the client interface to the server APIs
//
class OctoClient {
2021-01-13 18:40:37 +01:00
readonly serverUrl: string
2021-02-10 01:34:54 +01:00
get token(): string {
return localStorage.getItem('sessionId') || ''
}
get readToken(): string {
const queryString = new URLSearchParams(window.location.search)
const readToken = queryString.get('r') || ''
return readToken
}
2020-10-20 21:50:53 +02:00
2021-02-10 01:34:54 +01:00
constructor(serverUrl?: string) {
this.serverUrl = serverUrl || window.location.origin
2020-10-22 18:46:06 +02:00
Utils.log(`OctoClient serverUrl: ${this.serverUrl}`)
2020-10-20 21:50:53 +02:00
}
private async getJson(response: Response, defaultValue: any): Promise<any> {
// The server may return null or malformed json
try {
const value = await response.json()
return value || defaultValue
} catch {
return defaultValue
}
}
2020-12-04 16:03:09 +01:00
async login(username: string, password: string): Promise<boolean> {
const path = '/api/v1/login'
const body = JSON.stringify({username, password, type: 'normal'})
const response = await fetch(this.serverUrl + path, {
method: 'POST',
headers: this.headers(),
body,
})
if (response.status !== 200) {
2020-12-04 16:03:09 +01:00
return false
}
const responseJson = (await this.getJson(response, {})) as {token?: string}
2021-02-10 23:09:06 +01:00
if (responseJson.token) {
localStorage.setItem('sessionId', responseJson.token)
return true
}
2020-12-04 16:03:09 +01:00
return false
}
2021-01-15 03:23:15 +01:00
logout() {
localStorage.removeItem('sessionId')
}
async register(email: string, username: string, password: string, token?: string): Promise<{code: number, json: any}> {
2020-12-04 16:03:09 +01:00
const path = '/api/v1/register'
2021-01-14 01:56:01 +01:00
const body = JSON.stringify({email, username, password, token})
2020-12-04 16:03:09 +01:00
const response = await fetch(this.serverUrl + path, {
method: 'POST',
headers: this.headers(),
body,
2021-01-21 19:16:40 +01:00
})
const json = (await this.getJson(response, {}))
2021-01-21 19:16:40 +01:00
return {code: response.status, json}
}
async changePassword(userId: string, oldPassword: string, newPassword: string): Promise<{code: number, json: any}> {
const path = `/api/v1/users/${encodeURIComponent(userId)}/changepassword`
const body = JSON.stringify({oldPassword, newPassword})
const response = await fetch(this.serverUrl + path, {
method: 'POST',
headers: this.headers(),
body,
2020-12-04 16:03:09 +01:00
})
const json = (await this.getJson(response, {}))
return {code: response.status, json}
2020-12-04 16:03:09 +01:00
}
private headers() {
2020-12-04 16:03:09 +01:00
return {
Accept: 'application/json',
'Content-Type': 'application/json',
Authorization: this.token ? 'Bearer ' + this.token : '',
'X-Requested-With': 'XMLHttpRequest',
2020-12-04 16:03:09 +01:00
}
}
async getMe(): Promise<IUser | undefined> {
2020-12-07 20:40:16 +01:00
const path = '/api/v1/users/me'
const response = await fetch(this.serverUrl + path, {headers: this.headers()})
if (response.status !== 200) {
return undefined
}
const user = (await this.getJson(response, {})) as IUser
2020-12-07 20:40:16 +01:00
return user
}
2021-01-20 00:12:54 +01:00
async getUser(userId: string): Promise<IUser | undefined> {
const path = `/api/v1/users/${encodeURIComponent(userId)}`
const response = await fetch(this.serverUrl + path, {headers: this.headers()})
if (response.status !== 200) {
return undefined
}
const user = (await this.getJson(response, {})) as IUser
2021-01-20 00:12:54 +01:00
return user
}
2020-11-12 19:16:59 +01:00
async getSubtree(rootId?: string, levels = 2): Promise<IBlock[]> {
2021-01-20 00:12:54 +01:00
let path = `/api/v1/blocks/${encodeURIComponent(rootId || '')}/subtree?l=${levels}`
2021-01-13 18:40:37 +01:00
if (this.readToken) {
path += `&read_token=${this.readToken}`
}
2020-12-04 16:03:09 +01:00
const response = await fetch(this.serverUrl + path, {headers: this.headers()})
if (response.status !== 200) {
return []
}
const blocks = (await this.getJson(response, [])) as IMutableBlock[]
this.fixBlocks(blocks)
return blocks
2020-10-20 21:50:53 +02:00
}
async exportFullArchive(): Promise<IBlock[]> {
2020-10-20 21:52:56 +02:00
const path = '/api/v1/blocks/export'
2020-12-04 16:03:09 +01:00
const response = await fetch(this.serverUrl + path, {headers: this.headers()})
if (response.status !== 200) {
return []
}
const blocks = (await this.getJson(response, [])) as IMutableBlock[]
this.fixBlocks(blocks)
return blocks
2020-10-20 21:50:53 +02:00
}
async importFullArchive(blocks: readonly IBlock[]): Promise<Response> {
2020-10-20 21:50:53 +02:00
Utils.log(`importFullArchive: ${blocks.length} blocks(s)`)
2021-03-03 00:35:44 +01:00
// blocks.forEach((block) => {
// Utils.log(`\t ${block.type}, ${block.id}`)
// })
2020-10-20 21:50:53 +02:00
const body = JSON.stringify(blocks)
2020-11-12 23:55:55 +01:00
return fetch(this.serverUrl + '/api/v1/blocks/import', {
method: 'POST',
2020-12-04 16:03:09 +01:00
headers: this.headers(),
2020-10-20 21:50:53 +02:00
body,
})
2020-10-20 21:50:53 +02:00
}
2020-10-21 22:20:00 +02:00
async getBlocksWithParent(parentId: string, type?: string): Promise<IBlock[]> {
2020-10-20 21:50:53 +02:00
let path: string
2020-10-21 22:20:00 +02:00
if (type) {
path = `/api/v1/blocks?parent_id=${encodeURIComponent(parentId)}&type=${encodeURIComponent(type)}`
2020-10-20 21:50:53 +02:00
} else {
path = `/api/v1/blocks?parent_id=${encodeURIComponent(parentId)}`
2020-10-20 21:50:53 +02:00
}
2020-10-21 22:20:00 +02:00
return this.getBlocksWithPath(path)
}
async getBlocksWithType(type: string): Promise<IBlock[]> {
const path = `/api/v1/blocks?type=${encodeURIComponent(type)}`
return this.getBlocksWithPath(path)
}
2020-10-20 21:50:53 +02:00
2020-10-21 22:20:00 +02:00
private async getBlocksWithPath(path: string): Promise<IBlock[]> {
2020-12-04 16:03:09 +01:00
const response = await fetch(this.serverUrl + path, {headers: this.headers()})
2021-01-13 03:49:08 +01:00
if (response.status !== 200) {
return []
}
const blocks = (await this.getJson(response, [])) as IMutableBlock[]
2020-10-20 21:50:53 +02:00
this.fixBlocks(blocks)
return blocks
2020-10-20 21:50:53 +02:00
}
2020-11-12 23:55:55 +01:00
// TODO: Remove this fixup code
2020-10-21 03:28:55 +02:00
fixBlocks(blocks: IMutableBlock[]): void {
if (!blocks) {
2020-10-20 21:50:53 +02:00
return
}
for (const block of blocks) {
if (!block.fields) {
2020-10-20 21:50:53 +02:00
block.fields = {}
}
if (block.type === 'image') {
if (!block.fields.fileId && block.fields.url) {
// Convert deprecated url to fileId
try {
const url = new URL(block.fields.url)
const path = url.pathname
const fileId = path.substring(path.lastIndexOf('/') + 1)
block.fields.fileId = fileId
} catch {
Utils.logError(`Failed to get fileId from url: ${block.fields.url}`)
}
}
}
2020-10-20 21:50:53 +02:00
}
}
2020-10-21 03:28:55 +02:00
async updateBlock(block: IMutableBlock): Promise<Response> {
2020-10-20 21:50:53 +02:00
block.updateAt = Date.now()
2020-11-12 23:55:55 +01:00
return this.insertBlocks([block])
2020-10-20 21:50:53 +02:00
}
2020-10-21 03:28:55 +02:00
async updateBlocks(blocks: IMutableBlock[]): Promise<Response> {
2020-10-20 21:50:53 +02:00
const now = Date.now()
blocks.forEach((block) => {
block.updateAt = now
2020-10-20 21:52:56 +02:00
})
2020-11-12 23:55:55 +01:00
return this.insertBlocks(blocks)
2020-10-20 21:50:53 +02:00
}
async deleteBlock(blockId: string): Promise<Response> {
2020-10-22 18:46:06 +02:00
Utils.log(`deleteBlock: ${blockId}`)
2020-11-12 23:55:55 +01:00
return fetch(this.serverUrl + `/api/v1/blocks/${encodeURIComponent(blockId)}`, {
2020-10-20 21:50:53 +02:00
method: 'DELETE',
2020-12-04 16:03:09 +01:00
headers: this.headers(),
2020-10-20 21:50:53 +02:00
})
}
async insertBlock(block: IBlock): Promise<Response> {
return this.insertBlocks([block])
2020-10-20 21:50:53 +02:00
}
async insertBlocks(blocks: IBlock[]): Promise<Response> {
Utils.log(`insertBlocks: ${blocks.length} blocks(s)`)
blocks.forEach((block) => {
2020-11-12 19:16:59 +01:00
Utils.log(`\t ${block.type}, ${block.id}, ${block.title?.substr(0, 50) || ''}`)
})
const body = JSON.stringify(blocks)
2020-11-12 23:55:55 +01:00
return fetch(this.serverUrl + '/api/v1/blocks', {
2020-10-20 21:50:53 +02:00
method: 'POST',
2020-12-04 16:03:09 +01:00
headers: this.headers(),
2020-10-20 21:50:53 +02:00
body,
})
}
2021-01-13 01:52:25 +01:00
// Sharing
async getSharing(rootId: string): Promise<ISharing | undefined> {
2021-01-13 01:52:25 +01:00
const path = `/api/v1/sharing/${rootId}`
const response = await fetch(this.serverUrl + path, {headers: this.headers()})
if (response.status !== 200) {
return undefined
}
const sharing = (await this.getJson(response, undefined)) as ISharing
2021-01-13 01:52:25 +01:00
return sharing
}
async setSharing(sharing: ISharing): Promise<boolean> {
const path = `/api/v1/sharing/${sharing.id}`
const body = JSON.stringify(sharing)
const response = await fetch(
this.serverUrl + path,
{
method: 'POST',
headers: this.headers(),
body,
},
)
if (response.status !== 200) {
return false
2021-01-13 01:52:25 +01:00
}
return true
2021-01-13 01:52:25 +01:00
}
2021-01-14 01:56:01 +01:00
// Workspace
async getWorkspace(): Promise<IWorkspace | undefined> {
2021-01-14 01:56:01 +01:00
const path = '/api/v1/workspace'
const response = await fetch(this.serverUrl + path, {headers: this.headers()})
if (response.status !== 200) {
return undefined
}
const workspace = (await this.getJson(response, undefined)) as IWorkspace
2021-01-14 01:56:01 +01:00
return workspace
}
async regenerateWorkspaceSignupToken(): Promise<boolean> {
const path = '/api/v1/workspace/regenerate_signup_token'
const response = await fetch(this.serverUrl + path, {
method: 'POST',
headers: this.headers(),
})
if (response.status !== 200) {
return false
2021-01-14 01:56:01 +01:00
}
return true
2021-01-14 01:56:01 +01:00
}
2021-01-28 20:20:46 +01:00
// Files
2021-01-28 20:20:46 +01:00
// Returns fileId of uploaded file, or undefined on failure
async uploadFile(file: File): Promise<string | undefined> {
// IMPORTANT: We need to post the image as a form. The browser will convert this to a application/x-www-form-urlencoded POST
const formData = new FormData()
formData.append('file', file)
try {
const headers = this.headers() as Record<string, string>
// TIPTIP: Leave out Content-Type here, it will be automatically set by the browser
delete headers['Content-Type']
const response = await fetch(this.serverUrl + '/api/v1/files', {
method: 'POST',
headers,
body: formData,
})
if (response.status !== 200) {
return undefined
}
try {
const text = await response.text()
Utils.log(`uploadFile response: ${text}`)
const json = JSON.parse(text)
// const json = await this.getJson(response)
return json.fileId
} catch (e) {
Utils.logError(`uploadFile json ERROR: ${e}`)
}
} catch (e) {
Utils.logError(`uploadFile ERROR: ${e}`)
}
return undefined
}
async getFileAsDataUrl(fileId: string): Promise<string> {
const path = '/files/' + fileId
const response = await fetch(this.serverUrl + path, {headers: this.headers()})
2021-01-28 20:20:46 +01:00
if (response.status !== 200) {
return ''
}
const blob = await response.blob()
return URL.createObjectURL(blob)
}
2020-10-08 18:21:27 +02:00
}
2021-02-10 01:34:54 +01:00
const client = new OctoClient()
export default client