|
1 | | -import cheerio from 'cheerio' |
2 | | -import * as fs from 'fs-extra' |
3 | | -import {homedir} from 'os' |
4 | | -import * as path from 'path' |
5 | | -import nodeFetch from 'node-fetch' |
6 | | -import tough = require('tough-cookie') |
7 | | -import FileCookieStore from 'tough-cookie-filestore' |
8 | | -import * as url from 'url' |
9 | | - |
10 | | -import { defaults } from './utils' |
11 | | - |
12 | | -let version = '' |
13 | | -try { |
14 | | - version = require('../package.json').version |
15 | | -} catch (err) {} |
16 | | - |
17 | | -const defaultCookiePath = path.join(homedir(), '.hackmd', 'cookies.json') |
18 | | - |
19 | | -const defaultConfig = { |
20 | | - cookiePath: defaultCookiePath, |
21 | | - serverUrl: 'https://hackmd.io', |
22 | | - enterprise: true |
23 | | -} |
24 | | - |
25 | | -interface APIOptions { |
26 | | - serverUrl: string |
27 | | - cookiePath: string, |
28 | | - enterprise: boolean |
29 | | -} |
30 | | - |
31 | | -type nodeFetchType = (url: RequestInfo, init?: RequestInit | undefined) => Promise<Response> |
32 | | - |
33 | | -function encodeFormComponent(form: object) { |
34 | | - return Object.entries(form).map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`).join('&') |
35 | | -} |
36 | | - |
37 | | -export enum ExportType { |
38 | | - PDF, |
39 | | - SLIDE, |
40 | | - MD, |
41 | | - HTML |
42 | | -} |
43 | | - |
44 | | -export type HistoryItem = { |
45 | | - id: string |
46 | | - text: string |
47 | | - time: number | string |
48 | | - tags: string[] |
49 | | -} |
50 | | - |
51 | | -export type NewNoteOption = { |
52 | | - team: string |
53 | | -} |
54 | | - |
55 | | -/** |
56 | | - * codimd API Client |
57 | | - */ |
58 | | -class API { |
59 | | - public readonly serverUrl: string |
60 | | - public readonly enterprise: boolean |
61 | | - private readonly _fetch: nodeFetchType |
62 | | - |
63 | | - constructor(config: Partial<APIOptions> = {}) { |
64 | | - const {serverUrl, cookiePath, enterprise} = defaults(config, defaultConfig) |
65 | | - |
66 | | - fs.ensureFileSync(cookiePath) |
67 | | - |
68 | | - const jar = new FileCookieStore(cookiePath) |
69 | | - const fetch: nodeFetchType = require('fetch-cookie')(nodeFetch, new tough.CookieJar(jar as any)) |
70 | | - |
71 | | - this._fetch = fetch |
72 | | - this.serverUrl = url.parse(serverUrl).href |
73 | | - this.enterprise = enterprise |
74 | | - } |
75 | | - |
76 | | - async login(email: string, password: string) { |
77 | | - await this.fetch(url.resolve(this.serverUrl, 'login'), { |
78 | | - method: 'post', |
79 | | - body: encodeFormComponent({email, password}), |
80 | | - headers: await this.wrapHeaders({ |
81 | | - 'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8' |
82 | | - }) |
83 | | - }) |
84 | | - } |
85 | | - |
86 | | - async loginLdap(username: string, password: string) { |
87 | | - await this.fetch(url.resolve(this.serverUrl, 'auth/ldap'), { |
88 | | - method: 'post', |
89 | | - body: encodeFormComponent({username, password}), |
90 | | - headers: await this.wrapHeaders({ |
91 | | - 'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8' |
92 | | - }) |
93 | | - }) |
94 | | - } |
95 | | - |
96 | | - async logout() { |
97 | | - const response = await this.fetch(url.resolve(this.serverUrl, 'logout'), { |
98 | | - method: this.enterprise ? 'POST' : 'GET', |
99 | | - headers: await this.wrapHeaders({ |
100 | | - 'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8', |
101 | | - }) |
| 1 | +import axios, { AxiosInstance, AxiosRequestConfig, AxiosError } from 'axios' |
| 2 | +import { User } from './type' |
| 3 | + |
| 4 | +export class API { |
| 5 | + public axios: AxiosInstance |
| 6 | + constructor(accessToken: string) { |
| 7 | + this.axios = axios.create({ |
| 8 | + baseURL: "http://localhost:3000/v1/api", |
| 9 | + headers:{ |
| 10 | + "Content-Type": "application.json", |
| 11 | + } |
102 | 12 | }) |
103 | | - return response.status === 200 |
104 | | - } |
105 | | - |
106 | | - async isLogin() { |
107 | | - try { |
108 | | - const data = await this.getMe() |
109 | | - return data.status === 'ok' |
110 | | - } catch (err) { |
111 | | - return false |
112 | | - } |
113 | | - } |
114 | | - |
115 | | - async getMe() { |
116 | | - const response = await this.fetch(url.resolve(this.serverUrl, 'me'), this.defaultFetchOptions) |
117 | | - return response.json() |
118 | | - } |
119 | | - |
120 | | - async getHistory(): Promise<{ history: HistoryItem[] }> { |
121 | | - const response = await this.fetch(url.resolve(this.serverUrl, 'history'), this.defaultFetchOptions) |
122 | | - return response.json() |
123 | | - } |
124 | | - |
125 | | - async newNote(body: string, options?: NewNoteOption) { |
126 | | - let response |
127 | | - if (this.enterprise) { |
128 | | - let newNoteUrl |
129 | | - if (options?.team) { |
130 | | - newNoteUrl = url.resolve(this.serverUrl, `team/${options.team}/new`) |
131 | | - } else { |
132 | | - newNoteUrl = url.resolve(this.serverUrl, 'new') |
| 13 | + this.axios.interceptors.request.use( |
| 14 | + (config: AxiosRequestConfig) =>{ |
| 15 | + if (!config.headers) { |
| 16 | + config.headers = {}; |
| 17 | + } |
| 18 | + |
| 19 | + if (accessToken) { |
| 20 | + config.headers.Authorization = `Bearer ${accessToken}`; |
| 21 | + } |
| 22 | + |
| 23 | + return config |
| 24 | + }, |
| 25 | + (err: AxiosError) => { |
| 26 | + return Promise.reject(err) |
133 | 27 | } |
134 | | - |
135 | | - response = await this.fetch(newNoteUrl, { |
136 | | - method: 'POST', |
137 | | - body: encodeFormComponent({content: body}), |
138 | | - headers: await this.wrapHeaders({ |
139 | | - 'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8', |
140 | | - }) |
141 | | - }) |
142 | | - } else { |
143 | | - const contentType = 'text/markdown;charset=UTF-8' |
144 | | - response = await this.fetch(url.resolve(this.serverUrl, 'new'), { |
145 | | - method: 'POST', |
146 | | - body, |
147 | | - headers: await this.wrapHeaders({ |
148 | | - 'Content-Type': contentType |
149 | | - }) |
150 | | - }) |
151 | | - } |
152 | | - |
153 | | - if (response.status === 200) { |
154 | | - return response.url |
155 | | - } else { |
156 | | - throw new Error('Create note failed') |
157 | | - } |
158 | | - } |
159 | | - |
160 | | - async listNotes () { |
161 | | - // https://hackmd.io/api/overview?v=1627369059523 |
162 | | - if (this.enterprise) { |
163 | | - const response = await this.fetch(url.resolve(this.serverUrl, 'api/overview'), this.defaultFetchOptions) |
164 | | - |
165 | | - return response.json() |
166 | | - } else { |
167 | | - throw new Error('Not support') |
168 | | - } |
| 28 | + ) |
169 | 29 | } |
170 | 30 |
|
171 | | - async listTeamNotes (teamPath: string) { |
172 | | - if (this.enterprise) { |
173 | | - return this.fetch(url.resolve(this.serverUrl, `api/overview/team/${teamPath}`), this.defaultFetchOptions).then(res => res.json()) |
174 | | - } else { |
175 | | - throw new Error('Not support') |
176 | | - } |
177 | | - } |
178 | | - |
179 | | - private async exportRes(noteId: string, type: ExportType) { |
180 | | - let res: Response |
181 | | - switch (type) { |
182 | | - case ExportType.PDF: |
183 | | - res = await this.fetch(url.resolve(this.serverUrl, `${noteId}/pdf`), this.defaultFetchOptions) |
184 | | - break |
185 | | - case ExportType.HTML: |
186 | | - res = await this.fetch(url.resolve(this.serverUrl, `s/${noteId}`), this.defaultFetchOptions) |
187 | | - break |
188 | | - case ExportType.SLIDE: |
189 | | - res = await this.fetch(url.resolve(this.serverUrl, `${noteId}/slide`), this.defaultFetchOptions) |
190 | | - break |
191 | | - case ExportType.MD: |
192 | | - default: |
193 | | - res = await this.fetch(url.resolve(this.serverUrl, `${noteId}/download`), this.defaultFetchOptions) |
194 | | - } |
195 | | - |
196 | | - return res |
197 | | - } |
198 | | - |
199 | | - async exportString(noteId: string, type: ExportType) { |
200 | | - const res = await this.exportRes(noteId, type) |
201 | | - |
202 | | - return res.text() |
203 | | - } |
204 | | - |
205 | | - async exportStream (noteId: string, type: ExportType) { |
206 | | - const res = await this.exportRes(noteId, type) |
207 | | - |
208 | | - return res.body |
209 | | - } |
210 | | - |
211 | | - async getTeams () { |
212 | | - let data |
213 | | - try { |
214 | | - data = await this.getMe() |
215 | | - } catch (err) { |
216 | | - return [] |
217 | | - } |
218 | | - |
219 | | - return data.teams || [] |
220 | | - } |
221 | | - |
222 | | - get fetch() { |
223 | | - return this._fetch |
224 | | - } |
225 | | - |
226 | | - get domain() { |
227 | | - return url.parse(this.serverUrl).host |
| 31 | + getMe = async () => { |
| 32 | + const { data } = await this.axios.get<User>("/me") |
| 33 | + return data |
228 | 34 | } |
| 35 | +} |
229 | 36 |
|
230 | | - get defaultFetchOptions () { |
231 | | - return { |
232 | | - headers: { |
233 | | - 'User-Agent': `HackMD API Client ${version} Node.js` |
234 | | - } |
235 | | - } |
236 | | - } |
| 37 | +const api = new API("60WUTLO5DIHIOHCIY9TO0QEDVATU55XBTE7JJJBX9UTG49M7Y1") |
| 38 | +api.getMe() |
237 | 39 |
|
238 | | - private async wrapHeaders(headers: any) { |
239 | | - if (this.enterprise) { |
240 | | - const csrf = await this.loadCSRFToken() |
241 | | - return { |
242 | | - ...headers, |
243 | | - 'User-Agent': `HackMD API Client ${version} Node.js`, |
244 | | - 'X-XSRF-Token': csrf |
245 | | - } |
246 | | - } else { |
247 | | - return { |
248 | | - ...headers, |
249 | | - 'User-Agent': `HackMD API Client ${version} Node.js` |
250 | | - } |
251 | | - } |
252 | | - } |
253 | 40 |
|
254 | | - private async loadCSRFToken() { |
255 | | - const html = await this.fetch(this.serverUrl).then(r => r.text()) |
256 | | - const $ = cheerio.load(html) |
257 | 41 |
|
258 | | - return $('meta[name="csrf-token"]').attr('content') || '' |
259 | | - } |
260 | | -} |
261 | 42 |
|
262 | | -export default API |
0 commit comments