rmapi-js 5.0.0 → 6.0.0

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.
@@ -1,800 +0,0 @@
1
- import { describe, expect, test } from "bun:test";
2
- import { GenerationError, ResponseError, register, remarkable, } from ".";
3
- import { MockResponse, createMockFetch, resolveTo } from "./test-utils";
4
- // make sure we can't use fetch
5
- global.fetch = undefined;
6
- const TIMESTAMP = "1985-04-12T23:20:50.52Z";
7
- const GET_URL = JSON.stringify({
8
- url: "get url",
9
- method: "GET",
10
- relative_path: "get path",
11
- expires: TIMESTAMP,
12
- });
13
- const PUT_URL = JSON.stringify({
14
- url: "put url",
15
- method: "PUT",
16
- relative_path: "put path",
17
- expires: TIMESTAMP,
18
- });
19
- const PUT_URL_BYTES = JSON.stringify({
20
- url: "put url",
21
- method: "PUT",
22
- relative_path: "put path",
23
- expires: TIMESTAMP,
24
- maxuploadsize_bytes: 1000000,
25
- });
26
- const PUT_COLLECTION_RESPONSES = [
27
- // metadata
28
- new MockResponse(PUT_URL),
29
- new MockResponse(),
30
- // content
31
- new MockResponse(PUT_URL),
32
- new MockResponse(),
33
- // collection
34
- new MockResponse(PUT_URL),
35
- new MockResponse(),
36
- ];
37
- const PUT_FILE_RESPONSES = [
38
- // doc
39
- new MockResponse(PUT_URL_BYTES),
40
- new MockResponse(),
41
- // rest
42
- ...PUT_COLLECTION_RESPONSES,
43
- ];
44
- function encode(input) {
45
- const encoder = new TextEncoder();
46
- return encoder.encode(input).buffer;
47
- }
48
- describe("register()", () => {
49
- test("success", async () => {
50
- const fetch = createMockFetch(new MockResponse("custom device token"));
51
- const token = await register("academic", { fetch });
52
- expect(token).toBe("custom device token");
53
- expect(fetch.pastRequests.length).toBe(1);
54
- const [first] = fetch.pastRequests;
55
- expect(first).toBeDefined();
56
- });
57
- test("invalid", async () => {
58
- const fetch = createMockFetch();
59
- // eslint-disable-next-line @typescript-eslint/await-thenable
60
- await expect(register("", { fetch })).rejects.toThrow("code should be length 8, but was 0");
61
- });
62
- test("error", async () => {
63
- const fetch = createMockFetch(new MockResponse("", 400, "custom error"));
64
- // eslint-disable-next-line @typescript-eslint/await-thenable
65
- await expect(register("academic", { fetch })).rejects.toThrow("couldn't register api");
66
- });
67
- test("default", async () => {
68
- // can call with default syntax, even though this instance will fail
69
- // eslint-disable-next-line @typescript-eslint/await-thenable
70
- await expect(register("academic")).rejects.toThrow("fetch is not a function");
71
- });
72
- });
73
- describe("remarkable", () => {
74
- describe("remarkable()", () => {
75
- test("success", async () => {
76
- const fetch = createMockFetch(new MockResponse("custom user token"));
77
- await remarkable("custom device token", { fetch });
78
- expect(fetch.pastRequests.length).toBe(1);
79
- const [first] = fetch.pastRequests;
80
- expect(first?.headers?.["Authorization"]).toBe("Bearer custom device token");
81
- });
82
- test("error", async () => {
83
- const fetch = createMockFetch(new MockResponse("", 400));
84
- // eslint-disable-next-line @typescript-eslint/await-thenable
85
- await expect(remarkable("", { fetch })).rejects.toThrow("couldn't fetch auth token");
86
- });
87
- test("subtle error", async () => {
88
- const fetch = createMockFetch(new MockResponse("", 400));
89
- // eslint-disable-next-line @typescript-eslint/await-thenable
90
- await expect(remarkable("", { fetch, subtle: null })).rejects.toThrow("subtle was missing");
91
- });
92
- test("default", async () => {
93
- // eslint-disable-next-line @typescript-eslint/await-thenable
94
- await expect(remarkable("")).rejects.toThrow("fetch is not a function");
95
- });
96
- });
97
- describe("#authedFetch()", () => {
98
- test("error", async () => {
99
- const fetch = createMockFetch(new MockResponse(), new MockResponse("", 400));
100
- const api = await remarkable("", { fetch });
101
- // eslint-disable-next-line @typescript-eslint/await-thenable
102
- await expect(api.getRootHash()).rejects.toThrow("failed reMarkable request");
103
- });
104
- });
105
- describe("#getRootHash()", () => {
106
- test("success", async () => {
107
- const fetch = createMockFetch(new MockResponse(), new MockResponse(GET_URL), new MockResponse("custom hash", 200, "", {
108
- "x-goog-generation": "123",
109
- }), new MockResponse(GET_URL), new MockResponse("new hash", 200, "", { "x-goog-generation": "124" }));
110
- const api = await remarkable("", { fetch });
111
- const [hash, gen] = await api.getRootHash();
112
- expect(hash).toBe("custom hash");
113
- expect(gen).toBe(123n);
114
- // cached
115
- const [chash, cgen] = await api.getRootHash();
116
- expect(chash).toBe("custom hash");
117
- expect(cgen).toBe(123n);
118
- // not cached
119
- const [shash, sgen] = await api.getRootHash({ cache: false });
120
- expect(shash).toBe("new hash");
121
- expect(sgen).toBe(124n);
122
- });
123
- test("no generation", async () => {
124
- const fetch = createMockFetch(new MockResponse(), new MockResponse(GET_URL), new MockResponse("custom hash"));
125
- const api = await remarkable("", { fetch });
126
- // eslint-disable-next-line @typescript-eslint/await-thenable
127
- await expect(api.getRootHash()).rejects.toThrow("no generation header");
128
- });
129
- test("triple cache", async () => {
130
- // this should only make two requests, even though the first fails
131
- const [failedGet, send] = resolveTo(new MockResponse("err", 500));
132
- const fetch = createMockFetch(new MockResponse(), failedGet, new MockResponse(GET_URL), new MockResponse("custom hash", 200, "", {
133
- "x-goog-generation": "123",
134
- }));
135
- const api = await remarkable("", { fetch });
136
- const first = api.getRootHash(); // fails
137
- const second = api.getRootHash(); // succeeds
138
- const third = api.getRootHash(); // cached
139
- send(); // resolve first others waiting
140
- // eslint-disable-next-line @typescript-eslint/await-thenable
141
- await expect(first).rejects.toThrow("failed reMarkable request: err");
142
- const [shash, sgen] = await second;
143
- expect(shash).toBe("custom hash");
144
- expect(sgen).toBe(123n);
145
- const [thash, tgen] = await third;
146
- expect(thash).toBe("custom hash");
147
- expect(tgen).toBe(123n);
148
- });
149
- });
150
- describe("#signedFetch()", () => {
151
- test("error", async () => {
152
- const fetch = createMockFetch(new MockResponse(), new MockResponse(GET_URL), new MockResponse("custom error", 400, "bad request"));
153
- const api = await remarkable("", { fetch });
154
- // eslint-disable-next-line @typescript-eslint/await-thenable
155
- await expect(api.getRootHash()).rejects.toThrow("custom error");
156
- });
157
- });
158
- describe("#getUrl()", () => {
159
- test("error", async () => {
160
- const fetch = createMockFetch(new MockResponse(), new MockResponse("{}"));
161
- const api = await remarkable("", { fetch });
162
- // eslint-disable-next-line @typescript-eslint/await-thenable
163
- await expect(api.getRootHash()).rejects.toThrow("couldn't validate schema:");
164
- });
165
- });
166
- describe("#putRootHash()", () => {
167
- test("success", async () => {
168
- const fetch = createMockFetch(new MockResponse(), new MockResponse(PUT_URL), new MockResponse("custom hash", 200, "", {
169
- "x-goog-generation": "123",
170
- }));
171
- const api = await remarkable("", { fetch });
172
- const gen = await api.putRootHash("new hash", 0n);
173
- expect(gen).toBe(123n);
174
- });
175
- test("http error", async () => {
176
- const fetch = createMockFetch(new MockResponse(), new MockResponse(PUT_URL), new MockResponse("custom hash", 400));
177
- const api = await remarkable("", { fetch });
178
- // eslint-disable-next-line @typescript-eslint/await-thenable
179
- await expect(api.putRootHash("", 0n)).rejects.toThrow(ResponseError);
180
- });
181
- test("generation error", async () => {
182
- const fetch = createMockFetch(new MockResponse(), new MockResponse(PUT_URL), new MockResponse("custom hash", 412));
183
- const api = await remarkable("", { fetch });
184
- // eslint-disable-next-line @typescript-eslint/await-thenable
185
- await expect(api.putRootHash("", 0n)).rejects.toThrow(GenerationError);
186
- });
187
- test("no generation", async () => {
188
- const fetch = createMockFetch(new MockResponse(), new MockResponse(PUT_URL), new MockResponse("custom hash"));
189
- const api = await remarkable("", { fetch });
190
- // eslint-disable-next-line @typescript-eslint/await-thenable
191
- await expect(api.putRootHash("", 0n)).rejects.toThrow("no generation header");
192
- });
193
- });
194
- describe("#getText()", () => {
195
- test("default", async () => {
196
- const fetch = createMockFetch(new MockResponse(), new MockResponse(GET_URL), new MockResponse("custom text"), new MockResponse(GET_URL), new MockResponse("different text"));
197
- const api = await remarkable("", { fetch, cacheLimitBytes: 0 });
198
- const first = await api.getText("hash");
199
- expect(first).toBe("custom text");
200
- // no cache
201
- const second = await api.getText("hash");
202
- expect(second).toBe("different text");
203
- });
204
- test("cached", async () => {
205
- const fetch = createMockFetch(new MockResponse(), new MockResponse(GET_URL), new MockResponse("custom text"), new MockResponse(GET_URL), new MockResponse("different text"));
206
- const api = await remarkable("", { fetch });
207
- const first = await api.getText("hash");
208
- expect(first).toBe("custom text");
209
- // cache, no requests
210
- const second = await api.getText("hash");
211
- expect(second).toBe("custom text");
212
- // different key
213
- const third = await api.getText("new hash");
214
- expect(third).toBe("different text");
215
- });
216
- test("triple cache", async () => {
217
- // this tests that three simultaneous requests with a failure has the
218
- // appropriate behavior
219
- const [failedGet, send] = resolveTo(new MockResponse("err", 500));
220
- const fetch = createMockFetch(new MockResponse(), failedGet, new MockResponse(GET_URL), new MockResponse("custom text"));
221
- const api = await remarkable("", { fetch });
222
- const first = api.getText("hash"); // error
223
- const second = api.getText("hash"); // succeed
224
- const third = api.getText("hash"); // cache
225
- send(); // finish first request
226
- // eslint-disable-next-line @typescript-eslint/await-thenable
227
- await expect(first).rejects.toThrow("failed reMarkable request: err");
228
- // eslint-disable-next-line @typescript-eslint/await-thenable
229
- await expect(second).resolves.toBe("custom text");
230
- // eslint-disable-next-line @typescript-eslint/await-thenable
231
- await expect(third).resolves.toBe("custom text");
232
- });
233
- });
234
- describe("#getBuffer()", () => {
235
- test("success", async () => {
236
- const fetch = createMockFetch(new MockResponse(), new MockResponse(GET_URL), new MockResponse("custom text"));
237
- const api = await remarkable("", { fetch });
238
- const first = await api.getBuffer("hash");
239
- const dec = new TextDecoder();
240
- expect(dec.decode(first)).toBe("custom text");
241
- });
242
- test("no cache", async () => {
243
- const dec = new TextDecoder();
244
- const fetch = createMockFetch(new MockResponse(), new MockResponse(GET_URL), new MockResponse("custom text"), new MockResponse(GET_URL), new MockResponse("other text"));
245
- const api = await remarkable("", { fetch, cacheLimitBytes: 0 });
246
- const first = await api.getBuffer("hash");
247
- expect(dec.decode(first)).toBe("custom text");
248
- const second = await api.getBuffer("hash");
249
- expect(dec.decode(second)).toBe("other text");
250
- });
251
- });
252
- describe("#getMetadata()", () => {
253
- test("success", async () => {
254
- const fetch = createMockFetch(new MockResponse(), new MockResponse(GET_URL), new MockResponse(JSON.stringify({
255
- type: "CollectionType",
256
- visibleName: "title",
257
- parent: "",
258
- lastModified: TIMESTAMP,
259
- version: 1,
260
- synced: true,
261
- })));
262
- const api = await remarkable("", { fetch });
263
- const meta = await api.getMetadata("hash");
264
- expect(meta.visibleName).toBe("title");
265
- });
266
- test("issue #5", async () => {
267
- const fetch = createMockFetch(new MockResponse(), new MockResponse(GET_URL), new MockResponse(JSON.stringify({
268
- createdTime: "0",
269
- lastModified: "1699795915954",
270
- lastOpened: "1699795889468",
271
- lastOpenedPage: 227,
272
- // eslint-disable-next-line spellcheck/spell-checker
273
- parent: "44b07ec7-8bd1-43a3-a1ba-cd32f3222585",
274
- pinned: false,
275
- type: "DocumentType",
276
- visibleName: "Document",
277
- })));
278
- const api = await remarkable("", { fetch });
279
- const meta = await api.getMetadata("hash");
280
- expect(meta.visibleName).toBe("Document");
281
- });
282
- test("failure", async () => {
283
- const fetch = createMockFetch(new MockResponse(), new MockResponse(GET_URL), new MockResponse(JSON.stringify({
284
- type: "CollectionType",
285
- })));
286
- const api = await remarkable("", { fetch });
287
- // eslint-disable-next-line @typescript-eslint/await-thenable
288
- await expect(api.getMetadata("hash")).rejects.toThrow("couldn't validate schema");
289
- });
290
- });
291
- describe("#getEntries()", () => {
292
- test("success", async () => {
293
- const fetch = createMockFetch(new MockResponse(), new MockResponse(GET_URL), new MockResponse("3\n" + "hash:0:id:0:1234\n" + "other_hash:80000000:other_id:4:0\n"));
294
- const api = await remarkable("", { fetch });
295
- const [first, second] = await api.getEntries("");
296
- expect(first?.hash).toBe("hash");
297
- expect(first?.documentId).toBe("id");
298
- expect(first?.size).toBe(1234n);
299
- expect(second?.hash).toBe("other_hash");
300
- expect(second?.documentId).toBe("other_id");
301
- expect(second?.subfiles).toBe(4);
302
- });
303
- test("root", async () => {
304
- const fetch = createMockFetch(new MockResponse(), new MockResponse(GET_URL), new MockResponse("root hash", 200, "", {
305
- "x-goog-generation": "123",
306
- }), new MockResponse(GET_URL), new MockResponse("3\n" + "hash:0:id:0:1234\n" + "other_hash:80000000:other_id:4:0\n"));
307
- const api = await remarkable("", { fetch });
308
- const [first, second] = await api.getEntries();
309
- expect(first?.hash).toBe("hash");
310
- expect(first?.documentId).toBe("id");
311
- expect(first?.size).toBe(1234n);
312
- expect(second?.hash).toBe("other_hash");
313
- expect(second?.documentId).toBe("other_id");
314
- expect(second?.subfiles).toBe(4);
315
- const [, , , req] = fetch.pastRequests;
316
- expect(JSON.parse(req?.bodyText ?? "")
317
- .relative_path).toBe("root hash");
318
- });
319
- test("invalid format", async () => {
320
- const fetch = createMockFetch(new MockResponse(), new MockResponse(GET_URL), new MockResponse("3\nhash:0:id:0\n"));
321
- const api = await remarkable("", { fetch });
322
- // eslint-disable-next-line @typescript-eslint/await-thenable
323
- await expect(api.getEntries("")).rejects.toThrow("didn't contain five fields");
324
- });
325
- test("invalid document", async () => {
326
- const fetch = createMockFetch(new MockResponse(), new MockResponse(GET_URL), new MockResponse("3\nhash:0:id:3:2\n"));
327
- const api = await remarkable("", { fetch });
328
- // eslint-disable-next-line @typescript-eslint/await-thenable
329
- await expect(api.getEntries("")).rejects.toThrow("file type entry had nonzero number of subfiles: 3");
330
- });
331
- test("invalid type", async () => {
332
- const fetch = createMockFetch(new MockResponse(), new MockResponse(GET_URL), new MockResponse("3\nhash:1:id:3:2\n"));
333
- const api = await remarkable("", { fetch });
334
- // eslint-disable-next-line @typescript-eslint/await-thenable
335
- await expect(api.getEntries("")).rejects.toThrow("contained invalid type: 1");
336
- });
337
- test("invalid schema", async () => {
338
- const fetch = createMockFetch(new MockResponse(), new MockResponse(GET_URL), new MockResponse("4\nhash:1:id:3:2\n"));
339
- const api = await remarkable("", { fetch });
340
- // eslint-disable-next-line @typescript-eslint/await-thenable
341
- await expect(api.getEntries("")).rejects.toThrow("unexpected schema version: 4");
342
- });
343
- });
344
- describe("#putEntries()", () => {
345
- test("normal", async () => {
346
- const fetch = createMockFetch(new MockResponse(), new MockResponse(PUT_URL), new MockResponse());
347
- const api = await remarkable("", { fetch });
348
- const entry = await api.putEntries("doc id", [
349
- {
350
- type: "0",
351
- hash: "hash",
352
- documentId: "id",
353
- subfiles: 0,
354
- size: 1234n,
355
- },
356
- ]);
357
- expect(entry.documentId).toBe("doc id");
358
- expect(entry.subfiles).toBe(1);
359
- const [, , req] = fetch.pastRequests;
360
- expect(req?.url).toBe("put url");
361
- expect(req?.bodyText).toBe("3\nhash:0:id:0:1234\n");
362
- });
363
- test("cached", async () => {
364
- const fetch = createMockFetch(new MockResponse(), new MockResponse(PUT_URL), new MockResponse());
365
- const api = await remarkable("", { fetch });
366
- const entry = {
367
- type: "0",
368
- hash: "hash",
369
- documentId: "id",
370
- subfiles: 0,
371
- size: 1234n,
372
- };
373
- const { hash } = await api.putEntries("doc id", [entry]);
374
- const [cached] = await api.getEntries(hash);
375
- expect(cached).toEqual(entry);
376
- });
377
- });
378
- test("#putBuffer()", async () => {
379
- const fetch = createMockFetch(new MockResponse(), new MockResponse(PUT_URL), new MockResponse());
380
- const api = await remarkable("", { fetch });
381
- const buffer = new Uint8Array([0, 2, 5, 9, 100, 255]);
382
- const entry = await api.putBuffer("doc id", buffer.buffer);
383
- expect(entry.documentId).toBe("doc id");
384
- expect(entry.size).toBe(6n);
385
- const [, , req] = fetch.pastRequests;
386
- expect(req?.url).toBe("put url");
387
- const sent = new Uint8Array(req?.body);
388
- for (const [i, v] of buffer.entries()) {
389
- expect(sent[i]).toBe(v);
390
- }
391
- });
392
- describe("#putText()", () => {
393
- test("normal", async () => {
394
- const fetch = createMockFetch(new MockResponse(), new MockResponse(PUT_URL), new MockResponse());
395
- const api = await remarkable("", { fetch });
396
- const entry = await api.putText("doc id", "custom text");
397
- expect(entry.documentId).toBe("doc id");
398
- expect(entry.size).toBe(11n);
399
- const [, , req] = fetch.pastRequests;
400
- expect(req?.url).toBe("put url");
401
- expect(req?.bodyText).toBe("custom text");
402
- });
403
- test("cached", async () => {
404
- const fetch = createMockFetch(new MockResponse(), new MockResponse(PUT_URL), new MockResponse());
405
- const api = await remarkable("", { fetch });
406
- const { hash } = await api.putText("doc id", "custom text");
407
- const cached = await api.getText(hash);
408
- expect(cached).toBe("custom text");
409
- });
410
- test("no cache", async () => {
411
- const fetch = createMockFetch(new MockResponse(), new MockResponse(PUT_URL), new MockResponse(), new MockResponse(GET_URL), new MockResponse("different text"));
412
- const api = await remarkable("", { fetch, cacheLimitBytes: 0 });
413
- const { hash } = await api.putText("doc id", "custom text");
414
- const result = await api.getText(hash);
415
- expect(result).toBe("different text");
416
- });
417
- test("cache failure", async () => {
418
- const [failedPut, send] = resolveTo(new MockResponse("err", 500));
419
- const fetch = createMockFetch(new MockResponse(), failedPut, new MockResponse(PUT_URL), new MockResponse());
420
- const api = await remarkable("", { fetch });
421
- const first = api.putText("doc id", "custom text"); // fail
422
- const second = api.putText("doc id", "custom text"); // send
423
- const third = api.putText("doc id", "custom text"); // cached
424
- send();
425
- // eslint-disable-next-line @typescript-eslint/await-thenable
426
- await expect(first).rejects.toThrow("failed reMarkable request: err");
427
- await second;
428
- await third;
429
- // second failed
430
- expect(fetch.pastRequests).toHaveLength(4);
431
- });
432
- });
433
- test("#putCollection()", async () => {
434
- const fetch = createMockFetch(new MockResponse(), ...PUT_COLLECTION_RESPONSES);
435
- const api = await remarkable("", { fetch });
436
- await api.putCollection("New Folder");
437
- const [, , , metadata, , content] = fetch.pastRequests;
438
- expect(JSON.parse(metadata?.bodyText ?? "")).toBeDefined();
439
- expect(JSON.parse(content?.bodyText ?? "")).toBeDefined();
440
- });
441
- describe("#putEpub()", () => {
442
- test("success", async () => {
443
- const fetch = createMockFetch(new MockResponse(), ...PUT_FILE_RESPONSES);
444
- const epub = "fake epub content";
445
- const api = await remarkable("", { fetch });
446
- await api.putEpub("doc name", encode(epub));
447
- const [, , doc, , metadata, , content, , collection] = fetch.pastRequests;
448
- expect(doc?.bodyText).toBe(epub);
449
- expect(JSON.parse(metadata?.bodyText ?? "")).toBeDefined();
450
- expect(JSON.parse(content?.bodyText ?? "")).toBeDefined();
451
- expect(collection?.bodyText).toMatch(/^3\n/);
452
- });
453
- test("custom", async () => {
454
- const fetch = createMockFetch(new MockResponse(), ...PUT_FILE_RESPONSES);
455
- const epub = "fake epub content";
456
- const api = await remarkable("", { fetch });
457
- await api.putEpub("doc name", encode(epub), {
458
- cover: "first",
459
- lineHeight: "lg",
460
- margins: "rr",
461
- textScale: "sm",
462
- });
463
- const [, , , , , , content] = fetch.pastRequests;
464
- expect(JSON.parse(content?.bodyText ?? "").margins).toEqual(180);
465
- });
466
- });
467
- describe("#putPdf()", () => {
468
- test("success", async () => {
469
- const fetch = createMockFetch(new MockResponse(), ...PUT_FILE_RESPONSES);
470
- const pdf = "fake pdf content";
471
- const api = await remarkable("", { fetch });
472
- await api.putPdf("doc name", encode(pdf));
473
- const [, , doc, , metadata, , content, , collection] = fetch.pastRequests;
474
- expect(doc?.bodyText).toBe(pdf);
475
- expect(JSON.parse(metadata?.bodyText ?? "")).toBeDefined();
476
- expect(JSON.parse(content?.bodyText ?? "")).toBeDefined();
477
- expect(collection?.bodyText).toMatch(/^3\n/);
478
- });
479
- test("custom", async () => {
480
- const fetch = createMockFetch(new MockResponse(), ...PUT_FILE_RESPONSES);
481
- const pdf = "fake pdf content";
482
- const api = await remarkable("", { fetch });
483
- await api.putPdf("doc name", encode(pdf), {
484
- cover: "visited",
485
- });
486
- const [, , , , , , content] = fetch.pastRequests;
487
- expect(JSON.parse(content?.bodyText ?? "")
488
- .coverPageNumber).toEqual(-1);
489
- });
490
- });
491
- test("#syncComplete()", async () => {
492
- const fetch = createMockFetch(new MockResponse(), new MockResponse());
493
- const api = await remarkable("", { fetch });
494
- await api.syncComplete(0n);
495
- const [, req] = fetch.pastRequests;
496
- expect(req?.url).toBe("https://internal.cloud.remarkable.com/sync/v2/sync-complete");
497
- expect(JSON.parse(req?.bodyText ?? "")).toEqual({ generation: 0 });
498
- });
499
- describe("#create()", () => {
500
- const CREATE_RESPONSES = [
501
- // root hash
502
- new MockResponse(GET_URL),
503
- new MockResponse("custom hash", 200, "", {
504
- "x-goog-generation": "123",
505
- }),
506
- // entries
507
- new MockResponse(GET_URL),
508
- new MockResponse("3\n" + "hash:0:id:0:1234\n" + "other_hash:80000000:other_id:4:0\n"),
509
- // put entries
510
- new MockResponse(PUT_URL),
511
- new MockResponse(),
512
- // put root hash
513
- new MockResponse(PUT_URL),
514
- new MockResponse("custom hash", 200, "", {
515
- "x-goog-generation": "124",
516
- }),
517
- ];
518
- test("sync", async () => {
519
- const fetch = createMockFetch(new MockResponse(), ...CREATE_RESPONSES, new MockResponse());
520
- const api = await remarkable("", { fetch });
521
- const res = await api.create({
522
- type: "80000000",
523
- hash: "create hash",
524
- documentId: "docid",
525
- subfiles: 3,
526
- size: 0n,
527
- });
528
- expect(res).toBe(true);
529
- const [, , , , , , ents, , , sync] = fetch.pastRequests;
530
- expect(ents?.bodyText).toEqual("3\n" +
531
- "create hash:80000000:docid:3:0\n" +
532
- "hash:0:id:0:1234\n" +
533
- "other_hash:80000000:other_id:4:0\n");
534
- expect(JSON.parse(sync?.bodyText ?? "")).toEqual({ generation: 124 });
535
- });
536
- test("sync failure", async () => {
537
- const fetch = createMockFetch(new MockResponse(), ...CREATE_RESPONSES,
538
- // sync failure
539
- new MockResponse("", 400));
540
- const api = await remarkable("", { fetch });
541
- const res = await api.create({
542
- type: "80000000",
543
- hash: "create hash",
544
- documentId: "docid",
545
- subfiles: 3,
546
- size: 0n,
547
- });
548
- expect(res).toBe(false);
549
- const [, , , , , , ents, , , sync] = fetch.pastRequests;
550
- expect(ents?.bodyText).toEqual("3\n" +
551
- "create hash:80000000:docid:3:0\n" +
552
- "hash:0:id:0:1234\n" +
553
- "other_hash:80000000:other_id:4:0\n");
554
- expect(sync).toBeDefined();
555
- });
556
- test("no sync", async () => {
557
- const fetch = createMockFetch(new MockResponse(), ...CREATE_RESPONSES);
558
- const api = await remarkable("", { fetch });
559
- const res = await api.create({
560
- type: "80000000",
561
- hash: "create hash",
562
- documentId: "docid",
563
- subfiles: 3,
564
- size: 0n,
565
- }, { sync: false });
566
- expect(res).toBe(false);
567
- const [, , , , , , ents, , , sync] = fetch.pastRequests;
568
- expect(ents?.bodyText).toEqual("3\n" +
569
- "create hash:80000000:docid:3:0\n" +
570
- "hash:0:id:0:1234\n" +
571
- "other_hash:80000000:other_id:4:0\n");
572
- expect(sync).toBeUndefined();
573
- });
574
- });
575
- describe("#move()", () => {
576
- const MOVE_INIT_RESPONSES = [
577
- // root hash
578
- new MockResponse(GET_URL),
579
- new MockResponse("old root hash", 200, "", {
580
- "x-goog-generation": "123",
581
- }),
582
- // entries
583
- new MockResponse(GET_URL),
584
- new MockResponse("3\n" + "hash:80000000:id:1:0\n" + "other_hash:80000000:other_id:4:0\n"),
585
- ];
586
- const MOVE_DEST_RESPONSES = [
587
- // dest entries
588
- new MockResponse(GET_URL),
589
- new MockResponse("3\n" + "other_meta_hash:0:other_id.metadata:0:1234\n"),
590
- // dest metadata
591
- new MockResponse(GET_URL),
592
- new MockResponse(JSON.stringify({
593
- type: "CollectionType",
594
- visibleName: "title",
595
- parent: "",
596
- lastModified: TIMESTAMP,
597
- version: 1,
598
- synced: true,
599
- })),
600
- ];
601
- const MOVE_FINAL_RESPONSES = [
602
- // doc entries
603
- new MockResponse(GET_URL),
604
- new MockResponse("3\n" +
605
- "meta_hash:0:id.metadata:0:1234\n" +
606
- "content_hash:0:id.content:0:1234\n"),
607
- // doc metadata
608
- new MockResponse(GET_URL),
609
- new MockResponse(JSON.stringify({
610
- type: "DocumentType",
611
- visibleName: "movie",
612
- parent: "",
613
- lastModified: TIMESTAMP,
614
- version: 1,
615
- synced: true,
616
- })),
617
- // put metadata
618
- new MockResponse(PUT_URL),
619
- new MockResponse(),
620
- // put entries
621
- new MockResponse(PUT_URL),
622
- new MockResponse(),
623
- // put entries
624
- new MockResponse(PUT_URL),
625
- new MockResponse(),
626
- // put root hash
627
- new MockResponse(PUT_URL),
628
- new MockResponse("next root hash", 200, "", {
629
- "x-goog-generation": "124",
630
- }),
631
- ];
632
- test("sync", async () => {
633
- const fetch = createMockFetch(new MockResponse(), ...MOVE_INIT_RESPONSES, ...MOVE_DEST_RESPONSES, ...MOVE_FINAL_RESPONSES, new MockResponse());
634
- const api = await remarkable("", { fetch });
635
- const res = await api.move("id", "other_id");
636
- expect(res).toBe(true);
637
- const [meta, , docEnts, , rootEnts, , , sync] = fetch.pastRequests.slice(14);
638
- expect(JSON.parse(meta?.bodyText ?? "").parent).toEqual("other_id");
639
- expect(docEnts?.bodyText).toBe("3\n" +
640
- "content_hash:0:id.content:0:1234\n" +
641
- "f48bde1d019ee87cecfcd8ce6b92ba3a65618d43246db2e71d56467ec7285211:0:id.metadata:0:132\n");
642
- expect(rootEnts?.bodyText).toBe("3\n" +
643
- "bd1cd5d8bbccec925ef5c76f1777b7daa149764bfa5f3f900fc1692ea3a28de2:80000000:id:2:0\n" +
644
- "other_hash:80000000:other_id:4:0\n");
645
- expect(JSON.parse(sync?.bodyText ?? "")).toEqual({ generation: 124 });
646
- });
647
- test("no sync trash", async () => {
648
- const fetch = createMockFetch(new MockResponse(), ...MOVE_INIT_RESPONSES, ...MOVE_FINAL_RESPONSES);
649
- const api = await remarkable("", { fetch });
650
- const res = await api.move("id", "trash", { sync: false });
651
- expect(res).toBe(false);
652
- const [meta, , docEnts, , rootEnts, , , sync] = fetch.pastRequests.slice(10);
653
- expect(JSON.parse(meta?.bodyText ?? "").parent).toEqual("trash");
654
- expect(docEnts?.bodyText).toBe("3\n" +
655
- "content_hash:0:id.content:0:1234\n" +
656
- "a1d5afcc058c7f895fc9f99c6bc1e207312943ceca158493d68c5ea0b7f2bd65:0:id.metadata:0:129\n");
657
- expect(rootEnts?.bodyText).toBe("3\n" +
658
- "f55aa7bd69ac2a173bf6e8270a506e9470cfc2b606c5716f2de8cb1e16894952:80000000:id:2:0\n" +
659
- "other_hash:80000000:other_id:4:0\n");
660
- expect(sync).toBeUndefined();
661
- });
662
- test("throws with missing dest", async () => {
663
- const fetch = createMockFetch(new MockResponse(), ...MOVE_INIT_RESPONSES);
664
- const api = await remarkable("", { fetch });
665
- const res = api.move("id", "missing", { sync: false });
666
- // eslint-disable-next-line @typescript-eslint/await-thenable
667
- await expect(res).rejects.toThrow("destination id not found: missing");
668
- });
669
- test("throws with wrong destination type", async () => {
670
- const fetch = createMockFetch(new MockResponse(),
671
- // root hash
672
- new MockResponse(GET_URL), new MockResponse("custom hash", 200, "", {
673
- "x-goog-generation": "123",
674
- }),
675
- // entries
676
- new MockResponse(GET_URL), new MockResponse("3\n" + "hash:80000000:id:1:0\n" + "other_hash:0:other_id:0:4\n"));
677
- const api = await remarkable("", { fetch });
678
- const res = api.move("id", "other_id", { sync: false });
679
- // eslint-disable-next-line @typescript-eslint/await-thenable
680
- await expect(res).rejects.toThrow("destination id was a raw file: other_id");
681
- });
682
- test("throws with no dest metadata", async () => {
683
- const fetch = createMockFetch(new MockResponse(), ...MOVE_INIT_RESPONSES,
684
- // dest entries
685
- new MockResponse(GET_URL), new MockResponse("3\n" + "hash:0:other_id.content:0:1234\n"));
686
- const api = await remarkable("", { fetch });
687
- const res = api.move("id", "other_id", { sync: false });
688
- // eslint-disable-next-line @typescript-eslint/await-thenable
689
- await expect(res).rejects.toThrow("destination id didn't have metadata: other_id");
690
- });
691
- test("throws with file destination", async () => {
692
- const fetch = createMockFetch(new MockResponse(), ...MOVE_INIT_RESPONSES,
693
- // dest entries
694
- new MockResponse(GET_URL), new MockResponse("3\n" + "hash:0:other_id.metadata:0:1234\n"),
695
- // dest metadata
696
- new MockResponse(GET_URL), new MockResponse(JSON.stringify({
697
- type: "DocumentType",
698
- visibleName: "title",
699
- parent: "",
700
- lastModified: TIMESTAMP,
701
- version: 1,
702
- synced: true,
703
- })));
704
- const api = await remarkable("", { fetch });
705
- const res = api.move("id", "other_id", { sync: false });
706
- // eslint-disable-next-line @typescript-eslint/await-thenable
707
- await expect(res).rejects.toThrow("destination id wasn't a collection: other_id");
708
- });
709
- test("throws with missing document", async () => {
710
- const fetch = createMockFetch(new MockResponse(), ...MOVE_INIT_RESPONSES);
711
- const api = await remarkable("", { fetch });
712
- const res = api.move("missing", "");
713
- // eslint-disable-next-line @typescript-eslint/await-thenable
714
- await expect(res).rejects.toThrow("document not found: missing");
715
- });
716
- test("throws with non-collection document", async () => {
717
- const fetch = createMockFetch(new MockResponse(),
718
- // root hash
719
- new MockResponse(GET_URL), new MockResponse("custom hash", 200, "", {
720
- "x-goog-generation": "123",
721
- }),
722
- // entries
723
- new MockResponse(GET_URL), new MockResponse("3\n" + "hash:80000000:id:1:0\n" + "other_hash:0:other_id:0:4\n"));
724
- const api = await remarkable("", { fetch });
725
- const res = api.move("other_id", "");
726
- // eslint-disable-next-line @typescript-eslint/await-thenable
727
- await expect(res).rejects.toThrow("document was a raw file: other_id");
728
- });
729
- test("throws with no document metadata", async () => {
730
- const fetch = createMockFetch(new MockResponse(), ...MOVE_INIT_RESPONSES,
731
- // doc entries
732
- new MockResponse(GET_URL), new MockResponse("3\n" + "content_hash:0:id.content:0:1234\n"));
733
- const api = await remarkable("", { fetch });
734
- const res = api.move("id", "");
735
- // eslint-disable-next-line @typescript-eslint/await-thenable
736
- await expect(res).rejects.toThrow("document didn't have metadata: id");
737
- });
738
- });
739
- test("#getEntriesMetadata()", async () => {
740
- const entries = [
741
- {
742
- type: "CollectionType",
743
- id: "id",
744
- hash: "hash",
745
- visibleName: "name",
746
- },
747
- ];
748
- const fetch = createMockFetch(new MockResponse(), new MockResponse(JSON.stringify(entries)));
749
- const api = await remarkable("", { fetch });
750
- const res = await api.getEntriesMetadata();
751
- expect(res).toEqual(entries);
752
- });
753
- test("#uploadEpub()", async () => {
754
- const fetch = createMockFetch(new MockResponse(), new MockResponse(JSON.stringify({ docID: "epub id", hash: "epub hash" })));
755
- const api = await remarkable("", { fetch });
756
- const content = "my epub content";
757
- const res = await api.uploadEpub("my epub title", encode(content));
758
- expect(res.docID).toBe("epub id");
759
- expect(res.hash).toBe("epub hash");
760
- const [, req] = fetch.pastRequests;
761
- expect(req?.bodyText).toBe("my epub content");
762
- });
763
- test("#uploadPdf()", async () => {
764
- const fetch = createMockFetch(new MockResponse(), new MockResponse(JSON.stringify({ docID: "pdf id", hash: "pdf hash" })));
765
- const api = await remarkable("", { fetch });
766
- const content = "my pdf content";
767
- const res = await api.uploadPdf("my pdf title", encode(content));
768
- expect(res.docID).toBe("pdf id");
769
- expect(res.hash).toBe("pdf hash");
770
- const [, req] = fetch.pastRequests;
771
- expect(req?.bodyText).toBe("my pdf content");
772
- });
773
- test("#getCache()", async () => {
774
- let initCache;
775
- {
776
- const [failedGet, send] = resolveTo(new MockResponse("err", 500));
777
- const fetch = createMockFetch(new MockResponse(), new MockResponse(GET_URL), new MockResponse("text"), failedGet);
778
- const api = await remarkable("", { fetch });
779
- const first = await api.getText("hash");
780
- expect(first).toBe("text");
781
- // this will throw when we try to get the cache
782
- const second = api.getText("other hash");
783
- const cache = api.getCache();
784
- send(); // failed request resolves
785
- initCache = await cache;
786
- // eslint-disable-next-line @typescript-eslint/await-thenable
787
- await expect(second).rejects.toThrow("failed reMarkable request: err");
788
- }
789
- {
790
- const fetch = createMockFetch(new MockResponse(), new MockResponse(GET_URL), new MockResponse("different"));
791
- const api = await remarkable("", { fetch, initCache });
792
- // still in cache
793
- const first = await api.getText("hash");
794
- expect(first).toBe("text");
795
- // not in cache since request failed
796
- const second = await api.getText("other hash");
797
- expect(second).toBe("different");
798
- }
799
- });
800
- });