rmapi-js 11.2.0 → 12.0.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 +3 -2
- package/dist/index.d.ts +73 -101
- package/dist/index.js +448 -89
- package/dist/raw.d.ts +59 -71
- package/dist/raw.js +224 -17
- package/dist/rmapi-js.esm.min.js +8 -8
- package/package.json +1 -1
package/dist/raw.d.ts
CHANGED
|
@@ -25,11 +25,19 @@ export type RequestMethod = "POST" | "GET" | "PUT" | "DELETE" | "PATCH" | "OPTIO
|
|
|
25
25
|
export type UploadMimeType = "application/pdf" | "application/epub+zip" | "folder";
|
|
26
26
|
/** the schema version */
|
|
27
27
|
export type SchemaVersion = 3 | 4;
|
|
28
|
-
/**
|
|
29
|
-
|
|
30
|
-
|
|
28
|
+
/**
|
|
29
|
+
* a reference to stored cloud data: an id paired with the hash of its state
|
|
30
|
+
*
|
|
31
|
+
* This is the canonical way to point at something in the cloud. The `id` names
|
|
32
|
+
* *what*: a document's uuid at the high level, or a stored file name (like
|
|
33
|
+
* `<id>.content`) at the low level. The `hash` names *which version*, and
|
|
34
|
+
* changes every time that data mutates. Reads take one of these, and mutations
|
|
35
|
+
* return a fresh one with the new hash.
|
|
36
|
+
*/
|
|
37
|
+
export interface ItemRef {
|
|
38
|
+
/** the id of the referenced data: a document uuid, or a stored file name */
|
|
31
39
|
id: string;
|
|
32
|
-
/** the
|
|
40
|
+
/** the hash of the referenced state */
|
|
33
41
|
hash: string;
|
|
34
42
|
}
|
|
35
43
|
/**
|
|
@@ -40,13 +48,9 @@ export interface SimpleEntry {
|
|
|
40
48
|
* files, the high level entry will have the same hash and id as the low-level
|
|
41
49
|
* entry for that collection.
|
|
42
50
|
*/
|
|
43
|
-
export interface RawEntry {
|
|
51
|
+
export interface RawEntry extends ItemRef {
|
|
44
52
|
/** 80000000 for schema 3 collection type or 0 for schema 4 or schema 3 files or */
|
|
45
53
|
type: 80000000 | 0;
|
|
46
|
-
/** the hash of the collection this points to */
|
|
47
|
-
hash: string;
|
|
48
|
-
/** the unique id of the collection */
|
|
49
|
-
id: string;
|
|
50
54
|
/** the number of subfiles */
|
|
51
55
|
subfiles: number;
|
|
52
56
|
/** the total size of everything in the collection */
|
|
@@ -325,6 +329,8 @@ export interface LegacyDocumentContent extends CommonDocumentContent {
|
|
|
325
329
|
* metadata about it.
|
|
326
330
|
*/
|
|
327
331
|
export interface TemplateContent {
|
|
332
|
+
/** the template's own id, when present */
|
|
333
|
+
id?: string;
|
|
328
334
|
/** the template name */
|
|
329
335
|
name: string;
|
|
330
336
|
/** the template's author */
|
|
@@ -334,7 +340,7 @@ export interface TemplateContent {
|
|
|
334
340
|
/** category names this template belongs to (eg: "Planning", "Productivity") */
|
|
335
341
|
categories: string[];
|
|
336
342
|
/** labels associated with this template (eg: "Project management") */
|
|
337
|
-
labels
|
|
343
|
+
labels?: string[];
|
|
338
344
|
/** the orientation of this template */
|
|
339
345
|
orientation: "portrait" | "landscape";
|
|
340
346
|
/** semantic version for this template */
|
|
@@ -347,10 +353,15 @@ export interface TemplateContent {
|
|
|
347
353
|
* - `rm2`: reMarkable 2
|
|
348
354
|
* - `rmPP`: reMarkable Paper Pro
|
|
349
355
|
*/
|
|
350
|
-
supportedScreens
|
|
351
|
-
/**
|
|
356
|
+
supportedScreens?: ("rm2" | "rmPP")[];
|
|
357
|
+
/**
|
|
358
|
+
* named constants used by the `items` DSL
|
|
359
|
+
*
|
|
360
|
+
* A value is either a literal number or an expression string that references
|
|
361
|
+
* other constants and `templateWidth`, e.g. `"templateWidth - (offsetX * 2)"`.
|
|
362
|
+
*/
|
|
352
363
|
constants?: {
|
|
353
|
-
[name: string]: number;
|
|
364
|
+
[name: string]: number | string;
|
|
354
365
|
}[];
|
|
355
366
|
/** the template definition, an SVG-like DSL in JSON */
|
|
356
367
|
items: object[];
|
|
@@ -368,7 +379,7 @@ export interface Metadata {
|
|
|
368
379
|
/** [speculative] true if the item has been actually deleted */
|
|
369
380
|
deleted?: boolean;
|
|
370
381
|
/** the last modify time, the string of the epoch timestamp */
|
|
371
|
-
lastModified
|
|
382
|
+
lastModified?: string;
|
|
372
383
|
/** the last opened epoch timestamp, isn't defined for CollectionType */
|
|
373
384
|
lastOpened?: string;
|
|
374
385
|
/** the last page opened, isn't defined for CollectionType, starts at 0*/
|
|
@@ -410,6 +421,10 @@ export interface Metadata {
|
|
|
410
421
|
}
|
|
411
422
|
/** parse and validate the json text of a `.metadata` file */
|
|
412
423
|
export declare function parseMetadata(text: string): Metadata;
|
|
424
|
+
type AuthedFetch = (method: RequestMethod, url: string, init?: {
|
|
425
|
+
body?: string | Uint8Array;
|
|
426
|
+
headers?: Record<string, string>;
|
|
427
|
+
}) => Promise<Response>;
|
|
413
428
|
/**
|
|
414
429
|
* access to the low-level reMarkable api
|
|
415
430
|
*
|
|
@@ -469,7 +484,10 @@ export declare function parseMetadata(text: string): Metadata;
|
|
|
469
484
|
*
|
|
470
485
|
* Generally all hashes are 64 character hex strings, and all ids are uuid4.
|
|
471
486
|
*/
|
|
472
|
-
export
|
|
487
|
+
export declare class RawRemarkable {
|
|
488
|
+
#private;
|
|
489
|
+
constructor(authedFetch: AuthedFetch, cache: Map<string, string | null>, rawHost: string, uploadHost: string);
|
|
490
|
+
/** make an authorized request to remarkable */
|
|
473
491
|
/**
|
|
474
492
|
* gets the root hash and the current generation
|
|
475
493
|
*
|
|
@@ -482,66 +500,62 @@ export interface RawRemarkableApi {
|
|
|
482
500
|
/**
|
|
483
501
|
* get the raw binary data associated with a hash
|
|
484
502
|
*
|
|
485
|
-
* @param
|
|
486
|
-
* `<id>.docSchema` / `"root.docSchema"`
|
|
487
|
-
* validates
|
|
488
|
-
*
|
|
503
|
+
* @param ref - a reference to the stored file. Its `id` is the logical file
|
|
504
|
+
* name (`<id>.<ext>` for files, or `<id>.docSchema` / `"root.docSchema"`
|
|
505
|
+
* for entry indexes), which reMarkable validates against the rm-filename
|
|
506
|
+
* header. Sub-entries from {@link getEntries | `getEntries`} can be passed
|
|
507
|
+
* directly.
|
|
489
508
|
* @returns the data
|
|
490
509
|
*/
|
|
491
|
-
getHash(
|
|
510
|
+
getHash({ id: fileName, hash }: ItemRef): Promise<Uint8Array>;
|
|
492
511
|
/**
|
|
493
512
|
* get raw text data associated with a hash
|
|
494
513
|
*
|
|
495
514
|
* We assume text data are small, and so cache the entire text. If you want to
|
|
496
515
|
* avoid this, use {@link getHash | `getHash`} combined with a TextDecoder.
|
|
497
|
-
|
|
498
|
-
* @param
|
|
499
|
-
* @param hash - the hash to get text for
|
|
516
|
+
*
|
|
517
|
+
* @param ref - a reference to the stored file (see {@link getHash})
|
|
500
518
|
* @returns the text
|
|
501
519
|
*/
|
|
502
|
-
getText(
|
|
520
|
+
getText({ id: fileName, hash }: ItemRef): Promise<string>;
|
|
503
521
|
/**
|
|
504
522
|
* get the entries associated with a list hash
|
|
505
523
|
*
|
|
506
524
|
* A list hash is the root hash, or any hash with the type 80000000. NOTE
|
|
507
525
|
* these are hashed differently than files.
|
|
508
|
-
|
|
509
|
-
* @param
|
|
510
|
-
* for a sub-document's entry index
|
|
511
|
-
* @param hash - the hash to get entries for
|
|
526
|
+
*
|
|
527
|
+
* @param ref - a reference whose `id` is `"root.docSchema"` for the root, or
|
|
528
|
+
* `"<id>.docSchema"` for a sub-document's entry index
|
|
512
529
|
* @returns the entries
|
|
513
530
|
*/
|
|
514
|
-
getEntries(
|
|
531
|
+
getEntries(ref: ItemRef): Promise<Entries>;
|
|
515
532
|
/**
|
|
516
533
|
* get the parsed and validated `Content` of a content hash
|
|
517
534
|
*
|
|
518
535
|
* Use {@link getText | `getText`} combined with `JSON.parse` to bypass
|
|
519
536
|
* validation
|
|
520
|
-
|
|
521
|
-
* @param
|
|
522
|
-
* @param hash - the hash to get Content for
|
|
537
|
+
*
|
|
538
|
+
* @param ref - a reference to the stored file, typically `"<id>.content"`
|
|
523
539
|
* @returns the content
|
|
524
540
|
*/
|
|
525
|
-
getContent(
|
|
541
|
+
getContent(ref: ItemRef): Promise<Content>;
|
|
526
542
|
/**
|
|
527
543
|
* get the parsed and validated `Metadata` of a metadata hash
|
|
528
544
|
*
|
|
529
545
|
* Use {@link getText | `getText`} combined with `JSON.parse` to bypass
|
|
530
546
|
* validation
|
|
531
|
-
|
|
532
|
-
* @param
|
|
533
|
-
* @param hash - the hash to get Metadata for
|
|
547
|
+
*
|
|
548
|
+
* @param ref - a reference to the stored file, typically `"<id>.metadata"`
|
|
534
549
|
* @returns the metadata
|
|
535
550
|
*/
|
|
536
|
-
getMetadata(
|
|
551
|
+
getMetadata(ref: ItemRef): Promise<Metadata>;
|
|
537
552
|
/**
|
|
538
553
|
* get the parsed reMarkable lines (`.rm`) drawing of a page hash
|
|
539
|
-
|
|
540
|
-
* @param
|
|
541
|
-
* @param hash - the hash to get the page for
|
|
554
|
+
*
|
|
555
|
+
* @param ref - a reference to the stored file, typically `"<id>/<pageid>.rm"`
|
|
542
556
|
* @returns the parsed page
|
|
543
557
|
*/
|
|
544
|
-
getRm(
|
|
558
|
+
getRm(ref: ItemRef): Promise<RmPage>;
|
|
545
559
|
/**
|
|
546
560
|
* the same as {@link putFile | `putFile`} but rendering an `RmPage` to `.rm`
|
|
547
561
|
* bytes
|
|
@@ -585,7 +599,7 @@ export interface RawRemarkableApi {
|
|
|
585
599
|
*/
|
|
586
600
|
putFile(fileName: string, bytes: Uint8Array): Promise<[RawEntry, Promise<void>]>;
|
|
587
601
|
/** the same as {@link putFile | `putFile`} but with caching for text */
|
|
588
|
-
putText(fileName: string,
|
|
602
|
+
putText(fileName: string, text: string): Promise<[RawEntry, Promise<void>]>;
|
|
589
603
|
/** the same as {@link putText | `putText`} but with extra validation for Content */
|
|
590
604
|
putContent(fileName: string, content: Content): Promise<[RawEntry, Promise<void>]>;
|
|
591
605
|
/** the same as {@link putText | `putText`} but with extra validation for Metadata */
|
|
@@ -624,10 +638,10 @@ export interface RawRemarkableApi {
|
|
|
624
638
|
* @param visibleName - the name of the file as it should appear on the reMarkable
|
|
625
639
|
* @param bytes - the bytes of the file to upload
|
|
626
640
|
* @param mime - the mime type of the file to upload
|
|
627
|
-
|
|
641
|
+
|
|
628
642
|
* @returns a simple entry with the id and hash of the uploaded file
|
|
629
643
|
*/
|
|
630
|
-
uploadFile(visibleName: string, bytes: Uint8Array, mime: UploadMimeType): Promise<
|
|
644
|
+
uploadFile(visibleName: string, bytes: Uint8Array, mime: UploadMimeType): Promise<ItemRef>;
|
|
631
645
|
/**
|
|
632
646
|
* dump the current cache to a string to preserve between session
|
|
633
647
|
*
|
|
@@ -637,30 +651,4 @@ export interface RawRemarkableApi {
|
|
|
637
651
|
/** completely clear the cache */
|
|
638
652
|
clearCache(): void;
|
|
639
653
|
}
|
|
640
|
-
type
|
|
641
|
-
body?: string | Uint8Array;
|
|
642
|
-
headers?: Record<string, string>;
|
|
643
|
-
}) => Promise<Response>;
|
|
644
|
-
export declare class RawRemarkable implements RawRemarkableApi {
|
|
645
|
-
#private;
|
|
646
|
-
constructor(authedFetch: AuthedFetch, cache: Map<string, string | null>, rawHost: string, uploadHost: string);
|
|
647
|
-
/** make an authorized request to remarkable */
|
|
648
|
-
getRootHash(): Promise<[string, number, SchemaVersion]>;
|
|
649
|
-
getHash(fileName: string, hash: string): Promise<Uint8Array>;
|
|
650
|
-
getText(fileName: string, hash: string): Promise<string>;
|
|
651
|
-
getEntries(fileName: string, hash: string): Promise<Entries>;
|
|
652
|
-
getContent(fileName: string, hash: string): Promise<Content>;
|
|
653
|
-
getMetadata(fileName: string, hash: string): Promise<Metadata>;
|
|
654
|
-
getRm(fileName: string, hash: string): Promise<RmPage>;
|
|
655
|
-
putRm(fileName: string, page: RmPageV5): Promise<[RawEntry, Promise<void>]>;
|
|
656
|
-
putRootHash(hash: string, generation: number, broadcast?: boolean): Promise<[string, number]>;
|
|
657
|
-
putFile(fileName: string, bytes: Uint8Array): Promise<[RawEntry, Promise<void>]>;
|
|
658
|
-
putText(fileName: string, text: string): Promise<[RawEntry, Promise<void>]>;
|
|
659
|
-
putContent(fileName: string, content: Content): Promise<[RawEntry, Promise<void>]>;
|
|
660
|
-
putMetadata(fileName: string, metadata: Metadata): Promise<[RawEntry, Promise<void>]>;
|
|
661
|
-
putEntries(id: string, entries: readonly RawEntry[], schemaVersion: SchemaVersion): Promise<[RawEntry, Promise<void>]>;
|
|
662
|
-
uploadFile(visibleName: string, bytes: Uint8Array, mime: UploadMimeType): Promise<SimpleEntry>;
|
|
663
|
-
dumpCache(): string;
|
|
664
|
-
clearCache(): void;
|
|
665
|
-
}
|
|
666
|
-
export {};
|
|
654
|
+
export type { RawRemarkable as RawRemarkableApi };
|
package/dist/raw.js
CHANGED
|
@@ -174,19 +174,22 @@ const legacyDocumentContent = commonDocumentContent
|
|
|
174
174
|
.passthrough();
|
|
175
175
|
const templateContent = z
|
|
176
176
|
.object({
|
|
177
|
+
id: z.string().optional(),
|
|
177
178
|
name: z.string(),
|
|
178
179
|
author: z.string(),
|
|
179
180
|
iconData: z.string(),
|
|
180
181
|
categories: z.array(z.string()),
|
|
181
|
-
labels: z.array(z.string()),
|
|
182
|
+
labels: z.array(z.string()).optional(),
|
|
182
183
|
orientation: z.enum(["portrait", "landscape"]),
|
|
183
184
|
templateVersion: z.string(),
|
|
184
|
-
supportedScreens: z.array(z.enum(["rm2", "rmPP"])),
|
|
185
|
-
constants: z
|
|
185
|
+
supportedScreens: z.array(z.enum(["rm2", "rmPP"])).optional(),
|
|
186
|
+
constants: z
|
|
187
|
+
.array(z.record(z.string(), z.union([z.number(), z.string()])))
|
|
188
|
+
.optional(),
|
|
186
189
|
items: z.array(z.unknown()),
|
|
187
190
|
formatVersion: z.number().int().nonnegative().optional(),
|
|
188
191
|
})
|
|
189
|
-
.
|
|
192
|
+
.passthrough();
|
|
190
193
|
// content payloads aren't discriminable (legacy/modern differ only by tags
|
|
191
194
|
// element type), so this is an ordered union: the first matching variant wins
|
|
192
195
|
const content = z.union([
|
|
@@ -198,7 +201,7 @@ const content = z.union([
|
|
|
198
201
|
]);
|
|
199
202
|
const metadata = z
|
|
200
203
|
.object({
|
|
201
|
-
lastModified: z.string(),
|
|
204
|
+
lastModified: z.string().optional(),
|
|
202
205
|
parent: z.string(),
|
|
203
206
|
pinned: z.boolean(),
|
|
204
207
|
type: z.enum(["DocumentType", "CollectionType", "TemplateType"]),
|
|
@@ -233,7 +236,7 @@ const rootHash = z
|
|
|
233
236
|
schemaVersion: z.number().int().nonnegative(),
|
|
234
237
|
})
|
|
235
238
|
.passthrough();
|
|
236
|
-
const
|
|
239
|
+
const nativeItemRef = z
|
|
237
240
|
.object({
|
|
238
241
|
docID: z.string(),
|
|
239
242
|
hash: z.string(),
|
|
@@ -267,6 +270,65 @@ function parseRawEntryLine(line) {
|
|
|
267
270
|
throw new Error(`line '${line}' was not formatted correctly`);
|
|
268
271
|
}
|
|
269
272
|
}
|
|
273
|
+
/**
|
|
274
|
+
* access to the low-level reMarkable api
|
|
275
|
+
*
|
|
276
|
+
* This class gives more granualar access to the reMarkable cloud, but is more
|
|
277
|
+
* dangerous.
|
|
278
|
+
*
|
|
279
|
+
* ## Overview
|
|
280
|
+
*
|
|
281
|
+
* reMarkable uses an immutable file system, where each file is referenced by
|
|
282
|
+
* the 32 byte sha256 hash of its contents. Each file also has an id used to
|
|
283
|
+
* keep track of updates, so to "update" a file, you upload a new file, and
|
|
284
|
+
* change the hash associated with it's id.
|
|
285
|
+
*
|
|
286
|
+
* Each "item" (a document or a collection) is actually a list of files.
|
|
287
|
+
* The whole reMarkable state is then a list of these lists. Finally, the hash
|
|
288
|
+
* of that list is called the rootHash. To update anything, you have to update
|
|
289
|
+
* the root hash to point to a new list of updated items.
|
|
290
|
+
*
|
|
291
|
+
* This can be dangerous, as corrupting the root hash can destroy all of your
|
|
292
|
+
* files. It is therefore highly recommended to save your current root hash
|
|
293
|
+
* ({@link getRootHash | `getRootHash`}) before using this api to attempt file
|
|
294
|
+
* writes, so you can recover a previous "snapshot" should anything go wrong.
|
|
295
|
+
*
|
|
296
|
+
* ## Items
|
|
297
|
+
*
|
|
298
|
+
* Each item is a collection of individual files. Using
|
|
299
|
+
* {@link getEntries | `getEntries`} on the root hash will give you a list
|
|
300
|
+
* entries that correspond to items. Using `getEntries` on any of those items
|
|
301
|
+
* will get you the files that make up that item.
|
|
302
|
+
*
|
|
303
|
+
* The documented files are:
|
|
304
|
+
* - `<docid>.pdf` - a raw pdf document
|
|
305
|
+
* - `<docid>.epub` - a raw epub document
|
|
306
|
+
* - `<docid>.content` - a json file roughly describing document properties (see {@link DocumentContent | `DocumentContent`})
|
|
307
|
+
* - `<docid>.metadata` - metadata about the document (see {@link Metadata | `Metadata`})
|
|
308
|
+
* - `<docid>.pagedata` - a text file where each line is the template of that page
|
|
309
|
+
* - `<docid>/<pageid>.rm` - [speculative] raw remarkable vectors, text, etc
|
|
310
|
+
* - `<docid>/<pageid>-metadata.json` - [speculative] metadata about the individual page
|
|
311
|
+
* - `<docid>.highlights/<pageid>.json` - [speculative] highlights on the page
|
|
312
|
+
*
|
|
313
|
+
* Some items will have both a `.pdf` and `.epub` file, likely due to preparing
|
|
314
|
+
* for export. Collections only have `.content` and `.metadata` files, with
|
|
315
|
+
* `.content` only containing tags.
|
|
316
|
+
*
|
|
317
|
+
* ## Caching
|
|
318
|
+
*
|
|
319
|
+
* Since everything is tied to the hash of it's contents, we can agressively
|
|
320
|
+
* cache results. We assume that text contents are "small" and so fully cache
|
|
321
|
+
* them, where as binary files we treat as large and only store that we know
|
|
322
|
+
* they exist to prevent future writes.
|
|
323
|
+
*
|
|
324
|
+
* By default, this only persists as long as the api instance is alive. However,
|
|
325
|
+
* for performance reasons, you should call {@link dumpCache | `dumpCache`} to
|
|
326
|
+
* persist the cache between sessions.
|
|
327
|
+
*
|
|
328
|
+
* @remarks
|
|
329
|
+
*
|
|
330
|
+
* Generally all hashes are 64 character hex strings, and all ids are uuid4.
|
|
331
|
+
*/
|
|
270
332
|
export class RawRemarkable {
|
|
271
333
|
#authedFetch;
|
|
272
334
|
#rawHost;
|
|
@@ -288,6 +350,14 @@ export class RawRemarkable {
|
|
|
288
350
|
this.#uploadHost = uploadHost;
|
|
289
351
|
}
|
|
290
352
|
/** make an authorized request to remarkable */
|
|
353
|
+
/**
|
|
354
|
+
* gets the root hash and the current generation
|
|
355
|
+
*
|
|
356
|
+
* When calling `putRootHash`, you should pass the generation you got from
|
|
357
|
+
* this call. That way you tell reMarkable you're updating the previous state.
|
|
358
|
+
*
|
|
359
|
+
* @returns the root hash and the current generation
|
|
360
|
+
*/
|
|
291
361
|
async getRootHash() {
|
|
292
362
|
const res = await this.#authedFetch("GET", `${this.#rawHost}/sync/v4/root`);
|
|
293
363
|
const raw = await res.text();
|
|
@@ -312,7 +382,17 @@ export class RawRemarkable {
|
|
|
312
382
|
const raw = await resp.arrayBuffer();
|
|
313
383
|
return new Uint8Array(raw);
|
|
314
384
|
}
|
|
315
|
-
|
|
385
|
+
/**
|
|
386
|
+
* get the raw binary data associated with a hash
|
|
387
|
+
*
|
|
388
|
+
* @param ref - a reference to the stored file. Its `id` is the logical file
|
|
389
|
+
* name (`<id>.<ext>` for files, or `<id>.docSchema` / `"root.docSchema"`
|
|
390
|
+
* for entry indexes), which reMarkable validates against the rm-filename
|
|
391
|
+
* header. Sub-entries from {@link getEntries | `getEntries`} can be passed
|
|
392
|
+
* directly.
|
|
393
|
+
* @returns the data
|
|
394
|
+
*/
|
|
395
|
+
async getHash({ id: fileName, hash }) {
|
|
316
396
|
const cached = this.#cache.get(hash);
|
|
317
397
|
if (cached != null) {
|
|
318
398
|
const enc = new TextEncoder();
|
|
@@ -328,7 +408,16 @@ export class RawRemarkable {
|
|
|
328
408
|
return res;
|
|
329
409
|
}
|
|
330
410
|
}
|
|
331
|
-
|
|
411
|
+
/**
|
|
412
|
+
* get raw text data associated with a hash
|
|
413
|
+
*
|
|
414
|
+
* We assume text data are small, and so cache the entire text. If you want to
|
|
415
|
+
* avoid this, use {@link getHash | `getHash`} combined with a TextDecoder.
|
|
416
|
+
*
|
|
417
|
+
* @param ref - a reference to the stored file (see {@link getHash})
|
|
418
|
+
* @returns the text
|
|
419
|
+
*/
|
|
420
|
+
async getText({ id: fileName, hash }) {
|
|
332
421
|
const cached = this.#cache.get(hash);
|
|
333
422
|
if (cached != null) {
|
|
334
423
|
return cached;
|
|
@@ -342,8 +431,18 @@ export class RawRemarkable {
|
|
|
342
431
|
return res;
|
|
343
432
|
}
|
|
344
433
|
}
|
|
345
|
-
|
|
346
|
-
|
|
434
|
+
/**
|
|
435
|
+
* get the entries associated with a list hash
|
|
436
|
+
*
|
|
437
|
+
* A list hash is the root hash, or any hash with the type 80000000. NOTE
|
|
438
|
+
* these are hashed differently than files.
|
|
439
|
+
*
|
|
440
|
+
* @param ref - a reference whose `id` is `"root.docSchema"` for the root, or
|
|
441
|
+
* `"<id>.docSchema"` for a sub-document's entry index
|
|
442
|
+
* @returns the entries
|
|
443
|
+
*/
|
|
444
|
+
async getEntries(ref) {
|
|
445
|
+
const rawFile = await this.getText(ref);
|
|
347
446
|
const [version, ...rest] = rawFile.slice(0, -1).split("\n");
|
|
348
447
|
if (version === "3") {
|
|
349
448
|
return { entries: rest.map(parseRawEntryLine) };
|
|
@@ -371,20 +470,50 @@ export class RawRemarkable {
|
|
|
371
470
|
throw new Error(`schema version ${version} not supported`);
|
|
372
471
|
}
|
|
373
472
|
}
|
|
374
|
-
|
|
375
|
-
|
|
473
|
+
/**
|
|
474
|
+
* get the parsed and validated `Content` of a content hash
|
|
475
|
+
*
|
|
476
|
+
* Use {@link getText | `getText`} combined with `JSON.parse` to bypass
|
|
477
|
+
* validation
|
|
478
|
+
*
|
|
479
|
+
* @param ref - a reference to the stored file, typically `"<id>.content"`
|
|
480
|
+
* @returns the content
|
|
481
|
+
*/
|
|
482
|
+
async getContent(ref) {
|
|
483
|
+
const raw = await this.getText(ref);
|
|
376
484
|
const loaded = JSON.parse(raw);
|
|
377
485
|
return content.parse(loaded);
|
|
378
486
|
}
|
|
379
|
-
|
|
380
|
-
|
|
487
|
+
/**
|
|
488
|
+
* get the parsed and validated `Metadata` of a metadata hash
|
|
489
|
+
*
|
|
490
|
+
* Use {@link getText | `getText`} combined with `JSON.parse` to bypass
|
|
491
|
+
* validation
|
|
492
|
+
*
|
|
493
|
+
* @param ref - a reference to the stored file, typically `"<id>.metadata"`
|
|
494
|
+
* @returns the metadata
|
|
495
|
+
*/
|
|
496
|
+
async getMetadata(ref) {
|
|
497
|
+
const raw = await this.getText(ref);
|
|
381
498
|
const loaded = JSON.parse(raw);
|
|
382
499
|
return metadata.parse(loaded);
|
|
383
500
|
}
|
|
384
|
-
|
|
385
|
-
|
|
501
|
+
/**
|
|
502
|
+
* get the parsed reMarkable lines (`.rm`) drawing of a page hash
|
|
503
|
+
*
|
|
504
|
+
* @param ref - a reference to the stored file, typically `"<id>/<pageid>.rm"`
|
|
505
|
+
* @returns the parsed page
|
|
506
|
+
*/
|
|
507
|
+
async getRm(ref) {
|
|
508
|
+
const bytes = await this.getHash(ref);
|
|
386
509
|
return parseRm(bytes);
|
|
387
510
|
}
|
|
511
|
+
/**
|
|
512
|
+
* the same as {@link putFile | `putFile`} but rendering an `RmPage` to `.rm`
|
|
513
|
+
* bytes
|
|
514
|
+
*
|
|
515
|
+
* Only version 3 and 5 pages can be rendered; version 6 pages are read-only.
|
|
516
|
+
*/
|
|
388
517
|
async putRm(fileName, page) {
|
|
389
518
|
if (!fileName.endsWith(".rm")) {
|
|
390
519
|
throw new Error(`fileName ${fileName} did not end with '.rm'`);
|
|
@@ -393,6 +522,25 @@ export class RawRemarkable {
|
|
|
393
522
|
return await this.putFile(fileName, serializeRm(page));
|
|
394
523
|
}
|
|
395
524
|
}
|
|
525
|
+
/**
|
|
526
|
+
* update the current root hash
|
|
527
|
+
*
|
|
528
|
+
* This will fail if generation doesn't match the current server generation.
|
|
529
|
+
* This ensures that you are updating what you expect. IF you get a
|
|
530
|
+
* {@link GenerationError | `GenerationError`}, that indicates that the server
|
|
531
|
+
* was updated after you last got the generation. You should call
|
|
532
|
+
* {@link getRootHash | `getRootHash`} and then recompute the changes you want
|
|
533
|
+
* from the new root hash. If you ignore the update hash value and just call
|
|
534
|
+
* `putRootHash` again, you will overwrite the changes made by the other
|
|
535
|
+
* update.
|
|
536
|
+
*
|
|
537
|
+
* @param hash - the new root hash
|
|
538
|
+
* @param generation - the generation of the current root hash
|
|
539
|
+
* @param broadcast - [unknown] an option in the request
|
|
540
|
+
*
|
|
541
|
+
* @throws GenerationError if the generation doesn't match the current server generation
|
|
542
|
+
* @returns the new root hash and the new generation
|
|
543
|
+
*/
|
|
396
544
|
async putRootHash(hash, generation, broadcast = true) {
|
|
397
545
|
if (!Number.isSafeInteger(generation)) {
|
|
398
546
|
throw new Error(`generation ${generation} was not a safe integer`);
|
|
@@ -437,6 +585,20 @@ export class RawRemarkable {
|
|
|
437
585
|
}
|
|
438
586
|
}
|
|
439
587
|
}
|
|
588
|
+
/**
|
|
589
|
+
* put a raw onto the server
|
|
590
|
+
*
|
|
591
|
+
* This returns the new expeced entry of the file you uploaded, and a promise
|
|
592
|
+
* to finish the upload successful. By splitting these two operations you can
|
|
593
|
+
* start using the uploaded entry while file finishes uploading.
|
|
594
|
+
*
|
|
595
|
+
* NOTE: This won't update the state of the reMarkable until this entry is
|
|
596
|
+
* incorporated into the root hash.
|
|
597
|
+
*
|
|
598
|
+
* @param fileName - the file name to upload (e.g. `<id>.pdf`)
|
|
599
|
+
* @param bytes - the bytes to upload
|
|
600
|
+
* @returns the new entry and a promise to finish the upload
|
|
601
|
+
*/
|
|
440
602
|
async putFile(fileName, bytes) {
|
|
441
603
|
const hash = await digest(bytes);
|
|
442
604
|
const res = {
|
|
@@ -448,6 +610,7 @@ export class RawRemarkable {
|
|
|
448
610
|
};
|
|
449
611
|
return [res, this.#putFile(fileName, hash, bytes)];
|
|
450
612
|
}
|
|
613
|
+
/** the same as {@link putFile | `putFile`} but with caching for text */
|
|
451
614
|
async putText(fileName, text) {
|
|
452
615
|
const enc = new TextEncoder();
|
|
453
616
|
const bytes = enc.encode(text);
|
|
@@ -460,6 +623,7 @@ export class RawRemarkable {
|
|
|
460
623
|
}),
|
|
461
624
|
];
|
|
462
625
|
}
|
|
626
|
+
/** the same as {@link putText | `putText`} but with extra validation for Content */
|
|
463
627
|
async putContent(fileName, content) {
|
|
464
628
|
if (!fileName.endsWith(".content")) {
|
|
465
629
|
throw new Error(`fileName ${fileName} did not end with '.content'`);
|
|
@@ -468,6 +632,7 @@ export class RawRemarkable {
|
|
|
468
632
|
return await this.putText(fileName, JSON.stringify(content));
|
|
469
633
|
}
|
|
470
634
|
}
|
|
635
|
+
/** the same as {@link putText | `putText`} but with extra validation for Metadata */
|
|
471
636
|
async putMetadata(fileName, metadata) {
|
|
472
637
|
if (!fileName.endsWith(".metadata")) {
|
|
473
638
|
throw new Error(`fileName ${fileName} did not end with '.metadata'`);
|
|
@@ -476,6 +641,29 @@ export class RawRemarkable {
|
|
|
476
641
|
return await this.putText(fileName, JSON.stringify(metadata));
|
|
477
642
|
}
|
|
478
643
|
}
|
|
644
|
+
/**
|
|
645
|
+
* put a set of entries to make an entry list file
|
|
646
|
+
*
|
|
647
|
+
* To fully upload an item:
|
|
648
|
+
* 1. upload all the constituent files and metadata
|
|
649
|
+
* 2. call this with all of the entries
|
|
650
|
+
* 3. append this entry to the root entry and call this again to update this root list
|
|
651
|
+
* 4. put the new root hash
|
|
652
|
+
*
|
|
653
|
+
* NOTE: reMarkable currently rejects newly written schema 3 root indexes
|
|
654
|
+
* with a 400 "Software must be updated" error, even for accounts that still
|
|
655
|
+
* report schema 3, so the root list should always be written as schema 4. A
|
|
656
|
+
* warning is logged if a schema 3 root index is written.
|
|
657
|
+
*
|
|
658
|
+
* @param id - the id of the list to upload - this should be the item id if
|
|
659
|
+
* uploading an item list, or "root" if uploading a new root list. Note the
|
|
660
|
+
* asymmetry with {@link getEntries | `getEntries`}: `getEntries` takes the
|
|
661
|
+
* full `"<id>.docSchema"` file name, whereas `putEntries` takes the bare id
|
|
662
|
+
* and appends `.docSchema` (and special-cases `"root"`) itself.
|
|
663
|
+
* @param entries - the entries to upload
|
|
664
|
+
*
|
|
665
|
+
* @returns the new list entry and a promise to finish the upload
|
|
666
|
+
*/
|
|
479
667
|
async putEntries(id, entries, schemaVersion) {
|
|
480
668
|
if (id === "root" && schemaVersion === 3) {
|
|
481
669
|
console.warn('writing a schema 3 root index, which reMarkable rejects with a 400 "Software must be updated" error; write the root index with schema version 4 instead');
|
|
@@ -520,6 +708,19 @@ export class RawRemarkable {
|
|
|
520
708
|
};
|
|
521
709
|
return [res, this.#putFile(`${id}.docSchema`, hash, entryBuff)];
|
|
522
710
|
}
|
|
711
|
+
/**
|
|
712
|
+
* upload a file to the reMarkable cloud using the simple api
|
|
713
|
+
*
|
|
714
|
+
* This api is the same as used by the native reMarkable extension and works
|
|
715
|
+
* even if the backend schema version is version 4. Setting mime to "folder"
|
|
716
|
+
* allows folder creation.
|
|
717
|
+
*
|
|
718
|
+
* @param visibleName - the name of the file as it should appear on the reMarkable
|
|
719
|
+
* @param bytes - the bytes of the file to upload
|
|
720
|
+
* @param mime - the mime type of the file to upload
|
|
721
|
+
|
|
722
|
+
* @returns a simple entry with the id and hash of the uploaded file
|
|
723
|
+
*/
|
|
523
724
|
async uploadFile(visibleName, bytes, mime) {
|
|
524
725
|
const enc = new TextEncoder();
|
|
525
726
|
const meta = enc
|
|
@@ -534,12 +735,18 @@ export class RawRemarkable {
|
|
|
534
735
|
},
|
|
535
736
|
});
|
|
536
737
|
const loaded = (await resp.json());
|
|
537
|
-
const { docID, hash } =
|
|
738
|
+
const { docID, hash } = nativeItemRef.parse(loaded);
|
|
538
739
|
return { id: docID, hash };
|
|
539
740
|
}
|
|
741
|
+
/**
|
|
742
|
+
* dump the current cache to a string to preserve between session
|
|
743
|
+
*
|
|
744
|
+
* @returns a serialized version of the cache to pass to a new api instance
|
|
745
|
+
*/
|
|
540
746
|
dumpCache() {
|
|
541
747
|
return JSON.stringify(Object.fromEntries(this.#cache));
|
|
542
748
|
}
|
|
749
|
+
/** completely clear the cache */
|
|
543
750
|
clearCache() {
|
|
544
751
|
this.#cache.clear();
|
|
545
752
|
}
|