@aloth/olcli 0.15.3 → 0.16.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +140 -437
- package/dist/browser-auth.d.ts +144 -0
- package/dist/browser-auth.d.ts.map +1 -0
- package/dist/browser-auth.js +442 -0
- package/dist/browser-auth.js.map +1 -0
- package/dist/cli.js +429 -174
- package/dist/cli.js.map +1 -1
- package/dist/client.d.ts +20 -0
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +103 -37
- package/dist/client.js.map +1 -1
- package/dist/config.d.ts +25 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +40 -7
- package/dist/config.js.map +1 -1
- package/dist/errors.d.ts +50 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +115 -0
- package/dist/errors.js.map +1 -0
- package/dist/fast-export.d.ts +2 -0
- package/dist/fast-export.d.ts.map +1 -1
- package/dist/fast-export.js +5 -1
- package/dist/fast-export.js.map +1 -1
- package/dist/mcp-server.d.ts.map +1 -1
- package/dist/mcp-server.js +4 -1
- package/dist/mcp-server.js.map +1 -1
- package/dist/mcp.js +1 -1
- package/dist/mcp.js.map +1 -1
- package/dist/paths.d.ts +18 -0
- package/dist/paths.d.ts.map +1 -1
- package/dist/paths.js +45 -0
- package/dist/paths.js.map +1 -1
- package/dist/project-match.d.ts +37 -0
- package/dist/project-match.d.ts.map +1 -0
- package/dist/project-match.js +97 -0
- package/dist/project-match.js.map +1 -0
- package/dist/proxy.d.ts +52 -0
- package/dist/proxy.d.ts.map +1 -0
- package/dist/proxy.js +189 -0
- package/dist/proxy.js.map +1 -0
- package/dist/remote-helper.d.ts +3 -2
- package/dist/remote-helper.d.ts.map +1 -1
- package/dist/remote-helper.js +130 -37
- package/dist/remote-helper.js.map +1 -1
- package/dist/term.d.ts +25 -0
- package/dist/term.d.ts.map +1 -0
- package/dist/term.js +42 -0
- package/dist/term.js.map +1 -0
- package/package.json +9 -4
package/dist/client.js
CHANGED
|
@@ -18,16 +18,28 @@ import * as cheerio from 'cheerio';
|
|
|
18
18
|
import { readFileSync } from 'node:fs';
|
|
19
19
|
import { dirname, join } from 'node:path';
|
|
20
20
|
import { fileURLToPath } from 'node:url';
|
|
21
|
-
import
|
|
22
|
-
import * as http from 'node:http';
|
|
21
|
+
import { routeRequest } from './proxy.js';
|
|
23
22
|
import { RejectConflictError, buildUserDirectory, planRejection, renderRejectionDiff, describeAuthor, parseHistoryOtChanges, parseShareJsChanges } from './tracked-changes.js';
|
|
24
23
|
import { planEntityMove } from './move-plan.js';
|
|
24
|
+
import { matchProject } from './project-match.js';
|
|
25
|
+
import { HttpStatusError, SessionExpiredError, httpError, isAuthStatus } from './errors.js';
|
|
25
26
|
// Read version from package.json
|
|
26
27
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
27
28
|
const pkg = JSON.parse(readFileSync(join(__dirname, '..', 'package.json'), 'utf-8'));
|
|
28
29
|
const USER_AGENT = `olcli/${pkg.version}`;
|
|
29
30
|
const DEFAULT_BASE_URL = 'https://www.overleaf.com';
|
|
30
31
|
const RESOURCE_PATH_FAILURE_HINT = ' (Perhaps the resource file you specified does not exist in the project?)';
|
|
32
|
+
/**
|
|
33
|
+
* True when a redirect from `fromUrl` points at Overleaf's login page. A
|
|
34
|
+
* request to /login itself is the password flow and is never treated as one.
|
|
35
|
+
*/
|
|
36
|
+
export function isLoginRedirect(fromUrl, location) {
|
|
37
|
+
const from = new URL(fromUrl);
|
|
38
|
+
if (from.pathname === '/login')
|
|
39
|
+
return false;
|
|
40
|
+
const target = new URL(location, fromUrl);
|
|
41
|
+
return target.pathname === '/login' || target.pathname.endsWith('/login');
|
|
42
|
+
}
|
|
31
43
|
export const DEFAULT_COMMENT_CONCURRENCY = 4;
|
|
32
44
|
export const MAX_COMMENT_CONCURRENCY = 8;
|
|
33
45
|
/**
|
|
@@ -51,6 +63,8 @@ export class OverleafClient {
|
|
|
51
63
|
csrf;
|
|
52
64
|
baseUrl;
|
|
53
65
|
verbose = false;
|
|
66
|
+
/** Email of the signed-in account, when the bootstrap page named it. */
|
|
67
|
+
accountEmail;
|
|
54
68
|
timeoutMs = 10000;
|
|
55
69
|
// Cache per-project folder trees so repeated uploads in sync/upload calls
|
|
56
70
|
// don't re-fetch the tree via Socket.IO on every file.
|
|
@@ -163,21 +177,23 @@ export class OverleafClient {
|
|
|
163
177
|
expect: 'text'
|
|
164
178
|
});
|
|
165
179
|
if (!response.ok) {
|
|
166
|
-
throw
|
|
180
|
+
throw httpError('Failed to fetch projects page', response.status);
|
|
167
181
|
}
|
|
168
182
|
bootstrapClient.applySetCookieHeaders(response.headers['set-cookie']);
|
|
169
183
|
const html = response.body;
|
|
170
184
|
const $ = cheerio.load(html);
|
|
171
185
|
if (OverleafClient.isLoginPage($)) {
|
|
172
|
-
throw new
|
|
186
|
+
throw new SessionExpiredError('Overleaf answered with its login page');
|
|
173
187
|
}
|
|
174
188
|
const csrf = OverleafClient.extractCsrfToken($);
|
|
175
189
|
if (!csrf) {
|
|
176
|
-
throw new
|
|
190
|
+
throw new SessionExpiredError('No CSRF token on the project page');
|
|
177
191
|
}
|
|
178
192
|
// Update cookies if the bootstrap request added anything
|
|
179
193
|
const updatedCookies = bootstrapClient.cookies;
|
|
180
|
-
|
|
194
|
+
const client = new OverleafClient({ cookies: updatedCookies, csrf, baseUrl });
|
|
195
|
+
client.accountEmail = OverleafClient.extractAccountEmail($);
|
|
196
|
+
return client;
|
|
181
197
|
}
|
|
182
198
|
/**
|
|
183
199
|
* Create client by submitting Overleaf's email/password login form.
|
|
@@ -189,7 +205,7 @@ export class OverleafClient {
|
|
|
189
205
|
expect: 'text'
|
|
190
206
|
});
|
|
191
207
|
if (!loginPage.ok) {
|
|
192
|
-
throw new
|
|
208
|
+
throw new HttpStatusError('Failed to fetch login page', loginPage.status);
|
|
193
209
|
}
|
|
194
210
|
bootstrapClient.applySetCookieHeaders(loginPage.headers['set-cookie']);
|
|
195
211
|
const loginHtml = loginPage.body;
|
|
@@ -223,7 +239,7 @@ export class OverleafClient {
|
|
|
223
239
|
if (loginFailure) {
|
|
224
240
|
throw new Error('Password login failed. Email or password may be incorrect.');
|
|
225
241
|
}
|
|
226
|
-
throw new
|
|
242
|
+
throw new HttpStatusError('Password login failed', loginResponse.status);
|
|
227
243
|
}
|
|
228
244
|
const projectUrl = new URL(loginResponse.headers.location || '/project', baseUrl).toString();
|
|
229
245
|
const projectPage = await bootstrapClient.httpRequest(projectUrl, {
|
|
@@ -234,7 +250,7 @@ export class OverleafClient {
|
|
|
234
250
|
expect: 'text'
|
|
235
251
|
});
|
|
236
252
|
if (!projectPage.ok) {
|
|
237
|
-
throw
|
|
253
|
+
throw httpError('Failed to fetch projects page after login', projectPage.status);
|
|
238
254
|
}
|
|
239
255
|
bootstrapClient.applySetCookieHeaders(projectPage.headers['set-cookie']);
|
|
240
256
|
const projectHtml = projectPage.body;
|
|
@@ -246,7 +262,9 @@ export class OverleafClient {
|
|
|
246
262
|
if (!projectCsrf) {
|
|
247
263
|
throw new Error('Could not find CSRF token after password login.');
|
|
248
264
|
}
|
|
249
|
-
|
|
265
|
+
const client = new OverleafClient({ cookies: bootstrapClient.cookies, csrf: projectCsrf, baseUrl });
|
|
266
|
+
client.accountEmail = OverleafClient.extractAccountEmail($project) ?? email;
|
|
267
|
+
return client;
|
|
250
268
|
}
|
|
251
269
|
/**
|
|
252
270
|
* Pull the CSRF token out of a rendered Overleaf page.
|
|
@@ -282,6 +300,28 @@ export class OverleafClient {
|
|
|
282
300
|
}
|
|
283
301
|
return csrf;
|
|
284
302
|
}
|
|
303
|
+
/**
|
|
304
|
+
* The signed-in account's email from a rendered Overleaf page, when the
|
|
305
|
+
* page carries one. `ol-usersEmail` is on the project dashboard; `ol-user`
|
|
306
|
+
* (JSON) is on the editor and on older releases. Display only.
|
|
307
|
+
*/
|
|
308
|
+
static extractAccountEmail($) {
|
|
309
|
+
const direct = $('meta[name="ol-usersEmail"]').attr('content');
|
|
310
|
+
if (direct)
|
|
311
|
+
return direct;
|
|
312
|
+
const user = $('meta[name="ol-user"]').attr('content');
|
|
313
|
+
if (user) {
|
|
314
|
+
try {
|
|
315
|
+
const parsed = JSON.parse(user);
|
|
316
|
+
if (typeof parsed?.email === 'string' && parsed.email)
|
|
317
|
+
return parsed.email;
|
|
318
|
+
}
|
|
319
|
+
catch {
|
|
320
|
+
// Not JSON on this release; no email to show.
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
return undefined;
|
|
324
|
+
}
|
|
285
325
|
static isLoginPage($) {
|
|
286
326
|
return $('form[name="loginForm"]').length > 0 || $('input[name="password"]').length > 0;
|
|
287
327
|
}
|
|
@@ -349,12 +389,25 @@ export class OverleafClient {
|
|
|
349
389
|
}
|
|
350
390
|
const doRequest = (reqUrl, redirectsLeft) => {
|
|
351
391
|
return new Promise((resolve, reject) => {
|
|
352
|
-
const parsedUrl = new URL(reqUrl);
|
|
353
|
-
const transport = parsedUrl.protocol === 'https:' ? https : http;
|
|
354
392
|
const headers = this.normalizeHeaders({ ...extraHeaders, ...options.headers });
|
|
355
|
-
|
|
393
|
+
// HTTPS_PROXY / HTTP_PROXY / NO_PROXY: every request, the socket
|
|
394
|
+
// polling included, goes through here. See src/proxy.ts.
|
|
395
|
+
const route = routeRequest(reqUrl, headers);
|
|
396
|
+
if (route.proxy)
|
|
397
|
+
this.logVerbose(`${method} ${reqUrl} via proxy ${route.proxy.host}`);
|
|
398
|
+
const req = route.transport.request(route.url, { ...route.options, method }, (res) => {
|
|
356
399
|
const status = res.statusCode || 0;
|
|
357
400
|
const resHeaders = res.headers;
|
|
401
|
+
// Overleaf answers an expired session on any page with a redirect to
|
|
402
|
+
// /login. Following it would hand an HTML login form to a caller
|
|
403
|
+
// that expected JSON or a zip, and the user would see a parse error.
|
|
404
|
+
if (status >= 300 && status < 400 && res.headers.location
|
|
405
|
+
&& isLoginRedirect(reqUrl, res.headers.location)) {
|
|
406
|
+
this.logVerbose(`${method} ${reqUrl} -> ${status} redirect to login page`);
|
|
407
|
+
res.resume();
|
|
408
|
+
reject(new SessionExpiredError(`HTTP ${status} redirect to the login page from ${new URL(reqUrl).pathname}`, status));
|
|
409
|
+
return;
|
|
410
|
+
}
|
|
358
411
|
if (status >= 300 && status < 400 && res.headers.location && redirectsLeft > 0) {
|
|
359
412
|
this.logVerbose(`${method} ${reqUrl} -> ${status} redirect -> ${res.headers.location}`);
|
|
360
413
|
const redirectUrl = new URL(res.headers.location, reqUrl).toString();
|
|
@@ -416,7 +469,7 @@ export class OverleafClient {
|
|
|
416
469
|
expect: 'text'
|
|
417
470
|
});
|
|
418
471
|
if (!response.ok) {
|
|
419
|
-
throw
|
|
472
|
+
throw httpError('Failed to fetch projects', response.status);
|
|
420
473
|
}
|
|
421
474
|
this.applySetCookieHeaders(response.headers['set-cookie']);
|
|
422
475
|
const html = response.body;
|
|
@@ -489,6 +542,19 @@ export class OverleafClient {
|
|
|
489
542
|
const projects = await this.listProjects();
|
|
490
543
|
return projects.find(p => p.name === name);
|
|
491
544
|
}
|
|
545
|
+
/**
|
|
546
|
+
* Find a project by ID or by what a user typed for its name: exact name,
|
|
547
|
+
* then case-insensitive, then a unique prefix. Throws ProjectNotFoundError
|
|
548
|
+
* (with "Did you mean" suggestions) or AmbiguousProjectError.
|
|
549
|
+
* `getProject` keeps its exact-match contract for library callers.
|
|
550
|
+
*/
|
|
551
|
+
async findProject(query) {
|
|
552
|
+
const projects = await this.listProjects();
|
|
553
|
+
const byId = projects.find(p => p.id === query);
|
|
554
|
+
if (byId)
|
|
555
|
+
return byId;
|
|
556
|
+
return matchProject(projects, query);
|
|
557
|
+
}
|
|
492
558
|
/**
|
|
493
559
|
* Get project by ID
|
|
494
560
|
*/
|
|
@@ -518,7 +584,7 @@ export class OverleafClient {
|
|
|
518
584
|
expect: 'json'
|
|
519
585
|
});
|
|
520
586
|
if (!response.ok) {
|
|
521
|
-
throw
|
|
587
|
+
throw httpError('Failed to create project', response.status);
|
|
522
588
|
}
|
|
523
589
|
this.applySetCookieHeaders(response.headers['set-cookie']);
|
|
524
590
|
const projectId = response.body?.project_id;
|
|
@@ -541,7 +607,7 @@ export class OverleafClient {
|
|
|
541
607
|
expect: 'text'
|
|
542
608
|
});
|
|
543
609
|
if (!response.ok) {
|
|
544
|
-
throw
|
|
610
|
+
throw httpError('Failed to fetch project info', response.status);
|
|
545
611
|
}
|
|
546
612
|
this.applySetCookieHeaders(response.headers['set-cookie']);
|
|
547
613
|
const html = response.body;
|
|
@@ -697,7 +763,7 @@ export class OverleafClient {
|
|
|
697
763
|
timeoutMs
|
|
698
764
|
});
|
|
699
765
|
if (!response.ok) {
|
|
700
|
-
throw
|
|
766
|
+
throw httpError('Download failed', response.status);
|
|
701
767
|
}
|
|
702
768
|
this.applySetCookieHeaders(response.headers['set-cookie']);
|
|
703
769
|
return response.body;
|
|
@@ -722,7 +788,7 @@ export class OverleafClient {
|
|
|
722
788
|
expect: 'json'
|
|
723
789
|
});
|
|
724
790
|
if (!response.ok) {
|
|
725
|
-
throw
|
|
791
|
+
throw httpError('Failed to compile project', response.status);
|
|
726
792
|
}
|
|
727
793
|
this.applySetCookieHeaders(response.headers['set-cookie']);
|
|
728
794
|
const data = response.body;
|
|
@@ -770,7 +836,7 @@ export class OverleafClient {
|
|
|
770
836
|
throw new Error('Folder already exists');
|
|
771
837
|
}
|
|
772
838
|
if (!response.ok) {
|
|
773
|
-
throw
|
|
839
|
+
throw httpError('Failed to create folder', response.status);
|
|
774
840
|
}
|
|
775
841
|
this.applySetCookieHeaders(response.headers['set-cookie']);
|
|
776
842
|
const data = response.body;
|
|
@@ -894,9 +960,7 @@ export class OverleafClient {
|
|
|
894
960
|
timeoutMs: 5000
|
|
895
961
|
});
|
|
896
962
|
if (!handshakeResponse.ok) {
|
|
897
|
-
|
|
898
|
-
error.status = handshakeResponse.status;
|
|
899
|
-
throw error;
|
|
963
|
+
throw httpError('Failed to open project socket', handshakeResponse.status);
|
|
900
964
|
}
|
|
901
965
|
this.applySetCookieHeaders(handshakeResponse.headers['set-cookie']);
|
|
902
966
|
const sid = handshakeResponse.body.trim().split(':')[0];
|
|
@@ -932,7 +996,7 @@ export class OverleafClient {
|
|
|
932
996
|
timeoutMs: 7000
|
|
933
997
|
});
|
|
934
998
|
if (!response.ok) {
|
|
935
|
-
throw
|
|
999
|
+
throw httpError('Socket poll failed', response.status);
|
|
936
1000
|
}
|
|
937
1001
|
this.applySetCookieHeaders(response.headers['set-cookie']);
|
|
938
1002
|
const packets = this.decodeSocketIoPayload(response.body);
|
|
@@ -967,7 +1031,7 @@ export class OverleafClient {
|
|
|
967
1031
|
timeoutMs: 5000
|
|
968
1032
|
});
|
|
969
1033
|
if (!response.ok) {
|
|
970
|
-
throw
|
|
1034
|
+
throw httpError('Socket post failed', response.status);
|
|
971
1035
|
}
|
|
972
1036
|
this.applySetCookieHeaders(response.headers['set-cookie']);
|
|
973
1037
|
}
|
|
@@ -1468,6 +1532,8 @@ export class OverleafClient {
|
|
|
1468
1532
|
expect: 'text'
|
|
1469
1533
|
});
|
|
1470
1534
|
if (!response.ok) {
|
|
1535
|
+
if (isAuthStatus(response.status))
|
|
1536
|
+
throw httpError('Failed to upload file', response.status);
|
|
1471
1537
|
const text = response.body;
|
|
1472
1538
|
// Overleaf returns folder_not_found as HTTP 422 JSON.
|
|
1473
1539
|
// Parse the body first so caller can trigger folder probing fallback.
|
|
@@ -1531,7 +1597,7 @@ export class OverleafClient {
|
|
|
1531
1597
|
expect: 'text'
|
|
1532
1598
|
});
|
|
1533
1599
|
if (!response.ok) {
|
|
1534
|
-
throw
|
|
1600
|
+
throw httpError('Failed to delete entity', response.status);
|
|
1535
1601
|
}
|
|
1536
1602
|
this.applySetCookieHeaders(response.headers['set-cookie']);
|
|
1537
1603
|
}
|
|
@@ -1544,7 +1610,7 @@ export class OverleafClient {
|
|
|
1544
1610
|
expect: 'json'
|
|
1545
1611
|
});
|
|
1546
1612
|
if (!response.ok) {
|
|
1547
|
-
throw
|
|
1613
|
+
throw httpError('Failed to get entities', response.status);
|
|
1548
1614
|
}
|
|
1549
1615
|
this.applySetCookieHeaders(response.headers['set-cookie']);
|
|
1550
1616
|
const data = response.body;
|
|
@@ -1598,7 +1664,7 @@ export class OverleafClient {
|
|
|
1598
1664
|
expect: fileType === 'doc' ? 'json' : 'buffer'
|
|
1599
1665
|
});
|
|
1600
1666
|
if (!response.ok) {
|
|
1601
|
-
throw
|
|
1667
|
+
throw httpError('Failed to download file', response.status);
|
|
1602
1668
|
}
|
|
1603
1669
|
this.applySetCookieHeaders(response.headers['set-cookie']);
|
|
1604
1670
|
if (fileType === 'doc') {
|
|
@@ -1622,7 +1688,7 @@ export class OverleafClient {
|
|
|
1622
1688
|
expect: 'text'
|
|
1623
1689
|
});
|
|
1624
1690
|
if (!response.ok) {
|
|
1625
|
-
throw
|
|
1691
|
+
throw httpError('Failed to rename entity', response.status);
|
|
1626
1692
|
}
|
|
1627
1693
|
this.applySetCookieHeaders(response.headers['set-cookie']);
|
|
1628
1694
|
}
|
|
@@ -1645,7 +1711,7 @@ export class OverleafClient {
|
|
|
1645
1711
|
expect: 'text'
|
|
1646
1712
|
});
|
|
1647
1713
|
if (!response.ok) {
|
|
1648
|
-
throw
|
|
1714
|
+
throw httpError('Failed to rename project', response.status);
|
|
1649
1715
|
}
|
|
1650
1716
|
this.applySetCookieHeaders(response.headers['set-cookie']);
|
|
1651
1717
|
}
|
|
@@ -1672,7 +1738,7 @@ export class OverleafClient {
|
|
|
1672
1738
|
});
|
|
1673
1739
|
if (!response.ok) {
|
|
1674
1740
|
const detail = typeof response.body === 'string' && response.body.trim() ? ` (${response.body.trim().slice(0, 200)})` : '';
|
|
1675
|
-
throw
|
|
1741
|
+
throw httpError('Failed to move entity', response.status, detail.trim());
|
|
1676
1742
|
}
|
|
1677
1743
|
this.applySetCookieHeaders(response.headers['set-cookie']);
|
|
1678
1744
|
}
|
|
@@ -1798,7 +1864,7 @@ export class OverleafClient {
|
|
|
1798
1864
|
expect: 'json'
|
|
1799
1865
|
});
|
|
1800
1866
|
if (!response.ok) {
|
|
1801
|
-
throw
|
|
1867
|
+
throw httpError('Failed to fetch comment threads', response.status);
|
|
1802
1868
|
}
|
|
1803
1869
|
this.applySetCookieHeaders(response.headers['set-cookie']);
|
|
1804
1870
|
return response.body;
|
|
@@ -2032,7 +2098,7 @@ export class OverleafClient {
|
|
|
2032
2098
|
expect: 'text'
|
|
2033
2099
|
});
|
|
2034
2100
|
if (!response.ok) {
|
|
2035
|
-
throw
|
|
2101
|
+
throw httpError(`Failed to accept tracked changes in ${changes[0].path}`, response.status);
|
|
2036
2102
|
}
|
|
2037
2103
|
this.applySetCookieHeaders(response.headers['set-cookie']);
|
|
2038
2104
|
}
|
|
@@ -2179,7 +2245,7 @@ export class OverleafClient {
|
|
|
2179
2245
|
expect: 'text'
|
|
2180
2246
|
});
|
|
2181
2247
|
if (!response.ok) {
|
|
2182
|
-
throw
|
|
2248
|
+
throw httpError('Failed to resolve comment', response.status);
|
|
2183
2249
|
}
|
|
2184
2250
|
this.applySetCookieHeaders(response.headers['set-cookie']);
|
|
2185
2251
|
return comment;
|
|
@@ -2193,7 +2259,7 @@ export class OverleafClient {
|
|
|
2193
2259
|
expect: 'text'
|
|
2194
2260
|
});
|
|
2195
2261
|
if (!response.ok) {
|
|
2196
|
-
throw
|
|
2262
|
+
throw httpError('Failed to reopen comment', response.status);
|
|
2197
2263
|
}
|
|
2198
2264
|
this.applySetCookieHeaders(response.headers['set-cookie']);
|
|
2199
2265
|
return comment;
|
|
@@ -2207,7 +2273,7 @@ export class OverleafClient {
|
|
|
2207
2273
|
expect: 'text'
|
|
2208
2274
|
});
|
|
2209
2275
|
if (!response.ok) {
|
|
2210
|
-
throw
|
|
2276
|
+
throw httpError('Failed to delete comment', response.status);
|
|
2211
2277
|
}
|
|
2212
2278
|
this.applySetCookieHeaders(response.headers['set-cookie']);
|
|
2213
2279
|
return comment;
|
|
@@ -2313,7 +2379,7 @@ export class OverleafClient {
|
|
|
2313
2379
|
expect: 'text'
|
|
2314
2380
|
});
|
|
2315
2381
|
if (!response.ok) {
|
|
2316
|
-
throw
|
|
2382
|
+
throw httpError('Failed to post comment message', response.status);
|
|
2317
2383
|
}
|
|
2318
2384
|
this.applySetCookieHeaders(response.headers['set-cookie']);
|
|
2319
2385
|
if (!response.body)
|
|
@@ -2336,7 +2402,7 @@ export class OverleafClient {
|
|
|
2336
2402
|
expect: 'json'
|
|
2337
2403
|
});
|
|
2338
2404
|
if (!response.ok) {
|
|
2339
|
-
throw
|
|
2405
|
+
throw httpError('Failed to compile project', response.status);
|
|
2340
2406
|
}
|
|
2341
2407
|
this.applySetCookieHeaders(response.headers['set-cookie']);
|
|
2342
2408
|
const data = response.body;
|