Skip to content

Commit 6e21178

Browse files
committed
Extract api out as @hackmd/api
1 parent 8da65c4 commit 6e21178

File tree

4 files changed

+63
-258
lines changed

4 files changed

+63
-258
lines changed

package-lock.json

Lines changed: 59 additions & 55 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,33 +7,25 @@
77
},
88
"bugs": "https://github.com/hackmdio/codimd-cli/issues",
99
"dependencies": {
10+
"@hackmd/api": "^1.0.1",
1011
"@oclif/command": "^1.5.19",
1112
"@oclif/config": "^1.13.3",
1213
"@oclif/plugin-help": "^2.2.3",
13-
"cheerio": "^1.0.0-rc.3",
1414
"cli-ux": "^5.4.4",
15-
"fetch-cookie": "^0.7.3",
1615
"fs-extra": "^8.1.0",
1716
"inquirer": "^6.5.2",
1817
"lodash": "^4.17.15",
19-
"node-fetch": "^2.6.0",
20-
"tough-cookie": "^3.0.1",
21-
"tough-cookie-filestore": "0.0.1",
2218
"tslib": "^1.10.0"
2319
},
2420
"devDependencies": {
2521
"@oclif/dev-cli": "^1.22.2",
2622
"@oclif/test": "^1.2.5",
2723
"@oclif/tslint": "^3.1.1",
2824
"@types/chai": "^4.2.7",
29-
"@types/cheerio": "^0.22.15",
3025
"@types/fs-extra": "^8.0.1",
3126
"@types/inquirer": "^6.5.0",
3227
"@types/mocha": "^5.2.7",
3328
"@types/node": "^10.17.13",
34-
"@types/node-fetch": "^2.5.4",
35-
"@types/tough-cookie": "^2.3.6",
36-
"@types/tough-cookie-filestore": "0.0.0",
3729
"chai": "^4.2.0",
3830
"dotenv": "^8.2.0",
3931
"globby": "^10.0.2",

src/api.ts

Lines changed: 1 addition & 193 deletions
Original file line numberDiff line numberDiff line change
@@ -1,197 +1,5 @@
1-
import cheerio from 'cheerio'
2-
import * as fs from 'fs-extra'
3-
import nodeFetch from 'node-fetch'
4-
import tough = require('tough-cookie')
5-
import FileCookieStore from 'tough-cookie-filestore'
6-
import * as url from 'url'
1+
import API from '@hackmd/api'
72

83
import config from './config'
94

10-
interface APIOptions {
11-
serverUrl: string
12-
cookiePath: string,
13-
enterprise: boolean
14-
}
15-
16-
type nodeFetchType = (url: RequestInfo, init?: RequestInit | undefined) => Promise<Response>
17-
18-
function encodeFormComponent(form: object) {
19-
return Object.entries(form).map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`).join('&')
20-
}
21-
22-
export enum ExportType {
23-
PDF,
24-
SLIDE,
25-
MD,
26-
HTML
27-
}
28-
29-
export type HistoryItem = {
30-
id: string
31-
text: string
32-
time: number | string
33-
tags: string[]
34-
}
35-
36-
/**
37-
* codimd API Client
38-
*/
39-
class API {
40-
public readonly serverUrl: string
41-
private readonly enterprise: boolean
42-
private readonly _fetch: nodeFetchType
43-
44-
constructor(config: APIOptions) {
45-
const {serverUrl, cookiePath, enterprise} = config
46-
47-
fs.ensureFileSync(cookiePath)
48-
49-
const jar = new FileCookieStore(cookiePath)
50-
const fetch: nodeFetchType = require('fetch-cookie')(nodeFetch, new tough.CookieJar(jar as any))
51-
52-
this._fetch = fetch
53-
this.serverUrl = serverUrl
54-
this.enterprise = enterprise
55-
}
56-
57-
async login(email: string, password: string) {
58-
const response = await this.fetch(`${this.serverUrl}/login`, {
59-
method: 'post',
60-
body: encodeFormComponent({email, password}),
61-
headers: await this.wrapHeaders({
62-
'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8'
63-
})
64-
})
65-
66-
return response.status === 200
67-
}
68-
69-
async loginLdap(username: string, password: string) {
70-
const response = await this.fetch(`${this.serverUrl}/auth/ldap`, {
71-
method: 'post',
72-
body: encodeFormComponent({username, password}),
73-
headers: await this.wrapHeaders({
74-
'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8'
75-
})
76-
})
77-
return response.status === 200
78-
}
79-
80-
async logout() {
81-
const response = await this.fetch(`${this.serverUrl}/logout`, {
82-
method: this.enterprise ? 'POST' : 'GET',
83-
headers: await this.wrapHeaders({
84-
'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8',
85-
})
86-
})
87-
return response.status === 200
88-
}
89-
90-
async isLogin() {
91-
const response = await this.fetch(`${this.serverUrl}/me`)
92-
return response.status === 200
93-
}
94-
95-
async getMe() {
96-
const response = await this.fetch(`${this.serverUrl}/me`)
97-
return response.json()
98-
}
99-
100-
async getHistory(): Promise<{ history: HistoryItem[] }> {
101-
const response = await this.fetch(`${this.serverUrl}/history`)
102-
return response.json()
103-
}
104-
105-
async newNote(body: string) {
106-
let response
107-
if (this.enterprise) {
108-
response = await this.fetch(`${this.serverUrl}/new`, {
109-
method: 'POST',
110-
body: encodeFormComponent({content: body}),
111-
headers: await this.wrapHeaders({
112-
'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8',
113-
})
114-
})
115-
} else {
116-
const contentType = 'text/markdown;charset=UTF-8'
117-
response = await this.fetch(`${this.serverUrl}/new`, {
118-
method: 'POST',
119-
body,
120-
headers: {
121-
'Content-Type': contentType
122-
}
123-
})
124-
}
125-
126-
if (response.status === 200) {
127-
return response.url
128-
} else {
129-
throw new Error('Create note failed')
130-
}
131-
}
132-
133-
async export(noteId: string, type: ExportType, output: string) {
134-
let res: Response
135-
switch (type) {
136-
case ExportType.PDF:
137-
res = await this.fetch(`${this.serverUrl}/${noteId}/pdf`)
138-
break
139-
case ExportType.HTML:
140-
res = await this.fetch(`${this.serverUrl}/s/${noteId}`)
141-
break
142-
case ExportType.SLIDE:
143-
res = await this.fetch(`${this.serverUrl}/${noteId}/slide`)
144-
break
145-
case ExportType.MD:
146-
default:
147-
res = await this.fetch(`${this.serverUrl}/${noteId}/download`)
148-
}
149-
150-
return this.downloadFile(res, output)
151-
}
152-
153-
private async downloadFile(res: any, output: string) {
154-
const fileStream = fs.createWriteStream(output)
155-
156-
await new Promise((resolve, reject) => {
157-
res.body.pipe(fileStream)
158-
res.body.on('error', (err: any) => {
159-
reject(err)
160-
})
161-
fileStream.on('finish', function () {
162-
resolve()
163-
})
164-
})
165-
}
166-
167-
get fetch() {
168-
return this._fetch
169-
}
170-
171-
get domain() {
172-
return url.parse(this.serverUrl).host
173-
}
174-
175-
private async wrapHeaders(headers: any) {
176-
if (this.enterprise) {
177-
const csrf = await this.loadCSRFToken()
178-
return {
179-
...headers,
180-
'X-XSRF-Token': csrf
181-
}
182-
} else {
183-
return headers
184-
}
185-
}
186-
187-
private async loadCSRFToken() {
188-
const html = await this.fetch(`${this.serverUrl}`).then(r => r.text())
189-
const $ = cheerio.load(html)
190-
191-
return $('meta[name="csrf-token"]').attr('content') || ''
192-
}
193-
}
194-
195-
export default API
196-
1975
export const APIClient = new API(config)

0 commit comments

Comments
 (0)