rmapi-js 7.0.0 → 8.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.
- package/README.md +1 -0
- package/dist/index.d.ts +25 -8
- package/dist/index.js +201 -171
- package/dist/rmapi-js.esm.min.js +10 -10
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -73,6 +73,7 @@ this project.
|
|
|
73
73
|
|
|
74
74
|
- [✉️ Send Via](https://sendvia.me/) [[github](https://github.com/PaulKinlan/send-to-remarkable)] - upload to reMarkable via email
|
|
75
75
|
- [ⓡ rePub](https://chromewebstore.google.com/detail/repub/blkjpagbjaekkpojgcgdapmikoaolpbl) [[github](https://github.com/hafaio/repub)] - web clipper for reMarkable that supports images and customization
|
|
76
|
+
- [reMarkable Digest](https://digest.ferrucc.io) - create and receive a daily digest on your reMarkable
|
|
76
77
|
|
|
77
78
|
## Contributing
|
|
78
79
|
|
package/dist/index.d.ts
CHANGED
|
@@ -146,6 +146,12 @@ export declare class ValidationError extends Error {
|
|
|
146
146
|
readonly regex: RegExp;
|
|
147
147
|
constructor(field: string, regex: RegExp, message: string);
|
|
148
148
|
}
|
|
149
|
+
/** an error that results while supplying a hash not found in the entries of the root hash */
|
|
150
|
+
export declare class HashNotFoundError extends Error {
|
|
151
|
+
/** the hash that couldn't be found */
|
|
152
|
+
readonly hash: string;
|
|
153
|
+
constructor(hash: string);
|
|
154
|
+
}
|
|
149
155
|
/** options for registering with the api */
|
|
150
156
|
export interface RegisterOptions {
|
|
151
157
|
/**
|
|
@@ -671,9 +677,14 @@ export interface RemarkableApi {
|
|
|
671
677
|
* await api.listItems();
|
|
672
678
|
* ```
|
|
673
679
|
*
|
|
680
|
+
* @remarks
|
|
681
|
+
* This is now backed by the low level api, and you may notice some
|
|
682
|
+
* performance degradation if not taking advantage of the cache.
|
|
683
|
+
*
|
|
684
|
+
* @param refresh - if true, refresh the root hash before listing
|
|
674
685
|
* @returns a list of all items with some metadata
|
|
675
686
|
*/
|
|
676
|
-
listItems(): Promise<Entry[]>;
|
|
687
|
+
listItems(refresh?: boolean): Promise<Entry[]>;
|
|
677
688
|
/**
|
|
678
689
|
* similar to {@link listItems | `listItems`} but backed by the low level api
|
|
679
690
|
*
|
|
@@ -778,7 +789,7 @@ export interface RemarkableApi {
|
|
|
778
789
|
*/
|
|
779
790
|
putEpub(visibleName: string, buffer: Uint8Array, opts?: PutOptions): Promise<SimpleEntry>;
|
|
780
791
|
/** create a folder */
|
|
781
|
-
createFolder(visibleName: string, opts?: UploadOptions): Promise<SimpleEntry>;
|
|
792
|
+
createFolder(visibleName: string, opts?: UploadOptions, refresh?: boolean): Promise<SimpleEntry>;
|
|
782
793
|
/**
|
|
783
794
|
* upload an epub
|
|
784
795
|
*
|
|
@@ -787,6 +798,9 @@ export interface RemarkableApi {
|
|
|
787
798
|
* await api.uploadEpub("My EPub", ...);
|
|
788
799
|
* ```
|
|
789
800
|
*
|
|
801
|
+
* @remarks
|
|
802
|
+
* this is now simply a less powerful version of {@link putEpub | `putEpub`}.
|
|
803
|
+
*
|
|
790
804
|
* @param visibleName - the name to show for the uploaded epub
|
|
791
805
|
* @param buffer - the epub contents
|
|
792
806
|
*/
|
|
@@ -799,6 +813,9 @@ export interface RemarkableApi {
|
|
|
799
813
|
* await api.uploadPdf("My PDF", ...);
|
|
800
814
|
* ```
|
|
801
815
|
*
|
|
816
|
+
* @remarks
|
|
817
|
+
* this is now simply a less powerful version of {@link putPdf | `putPdf`}.
|
|
818
|
+
*
|
|
802
819
|
* @param visibleName - the name to show for the uploaded epub
|
|
803
820
|
* @param buffer - the epub contents
|
|
804
821
|
*/
|
|
@@ -814,7 +831,7 @@ export interface RemarkableApi {
|
|
|
814
831
|
* @param hash - the hash of the file to move
|
|
815
832
|
* @param parent - the id of the directory to move the entry to, "" (root) and "trash" are special parents
|
|
816
833
|
*/
|
|
817
|
-
move(hash: string, parent: string): Promise<HashEntry>;
|
|
834
|
+
move(hash: string, parent: string, refresh?: boolean): Promise<HashEntry>;
|
|
818
835
|
/**
|
|
819
836
|
* delete an entry
|
|
820
837
|
*
|
|
@@ -824,7 +841,7 @@ export interface RemarkableApi {
|
|
|
824
841
|
* ```
|
|
825
842
|
* @param hash - the hash of the entry to delete
|
|
826
843
|
*/
|
|
827
|
-
delete(hash: string): Promise<HashEntry>;
|
|
844
|
+
delete(hash: string, refresh?: boolean): Promise<HashEntry>;
|
|
828
845
|
/**
|
|
829
846
|
* rename an entry
|
|
830
847
|
*
|
|
@@ -835,7 +852,7 @@ export interface RemarkableApi {
|
|
|
835
852
|
* @param hash - the hash of the entry to rename
|
|
836
853
|
* @param visibleName - the new name to assign
|
|
837
854
|
*/
|
|
838
|
-
rename(hash: string, visibleName: string): Promise<HashEntry>;
|
|
855
|
+
rename(hash: string, visibleName: string, refresh?: boolean): Promise<HashEntry>;
|
|
839
856
|
/**
|
|
840
857
|
* move many entries
|
|
841
858
|
*
|
|
@@ -847,7 +864,7 @@ export interface RemarkableApi {
|
|
|
847
864
|
* @param hashes - an array of entry hashes to move
|
|
848
865
|
* @param parent - the directory id to move the entries to, "" (root) and "trash" are special ids
|
|
849
866
|
*/
|
|
850
|
-
bulkMove(hashes: readonly string[], parent: string): Promise<HashesEntry>;
|
|
867
|
+
bulkMove(hashes: readonly string[], parent: string, refresh?: boolean): Promise<HashesEntry>;
|
|
851
868
|
/**
|
|
852
869
|
* delete many entries
|
|
853
870
|
*
|
|
@@ -858,7 +875,7 @@ export interface RemarkableApi {
|
|
|
858
875
|
*
|
|
859
876
|
* @param hashes - the hashes of the entries to delete
|
|
860
877
|
*/
|
|
861
|
-
bulkDelete(hashes: readonly string[]): Promise<HashesEntry>;
|
|
878
|
+
bulkDelete(hashes: readonly string[], refresh?: boolean): Promise<HashesEntry>;
|
|
862
879
|
/**
|
|
863
880
|
* get the current cache value as a string
|
|
864
881
|
*
|
|
@@ -938,4 +955,4 @@ export interface RemarkableOptions {
|
|
|
938
955
|
* registered. Create one with {@link register}.
|
|
939
956
|
* @returns an api instance
|
|
940
957
|
*/
|
|
941
|
-
export declare function remarkable(deviceToken: string, { authHost,
|
|
958
|
+
export declare function remarkable(deviceToken: string, { authHost, rawHost, cache, maxCacheSize, }?: RemarkableOptions): Promise<RemarkableApi>;
|
package/dist/index.js
CHANGED
|
@@ -29,7 +29,6 @@
|
|
|
29
29
|
*
|
|
30
30
|
* const api = await remarkable(...);
|
|
31
31
|
* const entry = await api.putEpub("document name", epubBuffer);
|
|
32
|
-
* await api.create(entry);
|
|
33
32
|
* ```
|
|
34
33
|
*
|
|
35
34
|
* @remarks
|
|
@@ -55,12 +54,11 @@
|
|
|
55
54
|
import { fromByteArray } from "base64-js";
|
|
56
55
|
import CRC32C from "crc-32/crc32c";
|
|
57
56
|
import JSZip from "jszip";
|
|
58
|
-
import { boolean,
|
|
57
|
+
import { boolean, elements, enumeration, float64, int32, nullable, properties, string, timestamp, uint32, uint8, values, } from "jtd-ts";
|
|
59
58
|
import { v4 as uuid4 } from "uuid";
|
|
60
59
|
import { LruCache } from "./lru";
|
|
61
60
|
const AUTH_HOST = "https://webapp-prod.cloud.remarkable.engineering";
|
|
62
61
|
const RAW_HOST = "https://eu.tectonic.remarkable.com";
|
|
63
|
-
const SYNC_HOST = "https://web.eu.tectonic.remarkable.com";
|
|
64
62
|
const idReg = /^([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}||trash)$/;
|
|
65
63
|
const hashReg = /^[0-9a-f]{64}$/;
|
|
66
64
|
const tag = properties({
|
|
@@ -72,37 +70,6 @@ const pageTag = properties({
|
|
|
72
70
|
pageId: string(),
|
|
73
71
|
timestamp: float64(),
|
|
74
72
|
}, undefined, true);
|
|
75
|
-
const commonProperties = {
|
|
76
|
-
id: string(),
|
|
77
|
-
hash: string(),
|
|
78
|
-
visibleName: string(),
|
|
79
|
-
lastModified: string(),
|
|
80
|
-
pinned: boolean(),
|
|
81
|
-
};
|
|
82
|
-
const commonOptionalProperties = {
|
|
83
|
-
parent: string(),
|
|
84
|
-
tags: elements(properties({
|
|
85
|
-
name: string(),
|
|
86
|
-
timestamp: float64(),
|
|
87
|
-
}, undefined, true)),
|
|
88
|
-
};
|
|
89
|
-
const entry = discriminator("type", {
|
|
90
|
-
CollectionType: properties(commonProperties, commonOptionalProperties, true),
|
|
91
|
-
DocumentType: properties({
|
|
92
|
-
...commonProperties,
|
|
93
|
-
lastOpened: string(),
|
|
94
|
-
fileType: enumeration("epub", "pdf", "notebook"),
|
|
95
|
-
}, commonOptionalProperties, true),
|
|
96
|
-
});
|
|
97
|
-
const entries = elements(entry);
|
|
98
|
-
const uploadEntry = properties({
|
|
99
|
-
docID: string(),
|
|
100
|
-
hash: string(),
|
|
101
|
-
}, undefined, true);
|
|
102
|
-
const hashEntry = properties({ hash: string() }, undefined, true);
|
|
103
|
-
const hashesEntry = properties({
|
|
104
|
-
hashes: values(string()),
|
|
105
|
-
}, undefined, true);
|
|
106
73
|
/** An error that gets thrown when the backend while trying to update
|
|
107
74
|
*
|
|
108
75
|
* IF you encounter this error, you likely just need to try th request again. If
|
|
@@ -141,6 +108,15 @@ export class ValidationError extends Error {
|
|
|
141
108
|
this.regex = regex;
|
|
142
109
|
}
|
|
143
110
|
}
|
|
111
|
+
/** an error that results while supplying a hash not found in the entries of the root hash */
|
|
112
|
+
export class HashNotFoundError extends Error {
|
|
113
|
+
/** the hash that couldn't be found */
|
|
114
|
+
hash;
|
|
115
|
+
constructor(hash) {
|
|
116
|
+
super(`'${hash}' not found in the root hash`);
|
|
117
|
+
this.hash = hash;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
144
120
|
/**
|
|
145
121
|
* register a device and get the token needed to access the api
|
|
146
122
|
*
|
|
@@ -569,14 +545,12 @@ class RawRemarkable {
|
|
|
569
545
|
/** the implementation of that api */
|
|
570
546
|
class Remarkable {
|
|
571
547
|
#userToken;
|
|
572
|
-
#syncHost;
|
|
573
548
|
/** the same cache that underlies the raw api, allowing us to modify it */
|
|
574
549
|
#cache;
|
|
575
550
|
raw;
|
|
576
551
|
#lastHashGen;
|
|
577
|
-
constructor(userToken,
|
|
552
|
+
constructor(userToken, rawHost, cache) {
|
|
578
553
|
this.#userToken = userToken;
|
|
579
|
-
this.#syncHost = syncHost;
|
|
580
554
|
this.#cache = cache;
|
|
581
555
|
this.raw = new RawRemarkable((method, url, { body, headers } = {}) => this.#authedFetch(url, { method, body, headers }), cache, rawHost);
|
|
582
556
|
}
|
|
@@ -620,38 +594,51 @@ class Remarkable {
|
|
|
620
594
|
return resp;
|
|
621
595
|
}
|
|
622
596
|
}
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
597
|
+
async #convertEntry({ hash, id }) {
|
|
598
|
+
const entries = await this.raw.getEntries(hash);
|
|
599
|
+
const metaEnt = entries.find((ent) => ent.id.endsWith(".metadata"));
|
|
600
|
+
const contentEnt = entries.find((ent) => ent.id.endsWith(".content"));
|
|
601
|
+
if (metaEnt === undefined) {
|
|
602
|
+
throw new Error(`couldn't find metadata for hash ${hash}`);
|
|
603
|
+
}
|
|
604
|
+
else if (contentEnt === undefined) {
|
|
605
|
+
throw new Error(`couldn't find content for hash ${hash}`);
|
|
606
|
+
}
|
|
607
|
+
const [{ visibleName, lastModified, pinned, parent, lastOpened }, content] = await Promise.all([
|
|
608
|
+
this.raw.getMetadata(metaEnt.hash),
|
|
609
|
+
this.raw.getContent(contentEnt.hash),
|
|
610
|
+
]);
|
|
611
|
+
if (content.fileType === undefined) {
|
|
612
|
+
return {
|
|
613
|
+
id,
|
|
614
|
+
hash,
|
|
615
|
+
visibleName,
|
|
616
|
+
lastModified,
|
|
617
|
+
pinned,
|
|
618
|
+
parent,
|
|
619
|
+
tags: content.tags,
|
|
620
|
+
type: "CollectionType",
|
|
621
|
+
};
|
|
622
|
+
}
|
|
623
|
+
else {
|
|
624
|
+
return {
|
|
625
|
+
id,
|
|
626
|
+
hash,
|
|
627
|
+
visibleName,
|
|
628
|
+
lastModified,
|
|
629
|
+
pinned,
|
|
630
|
+
parent,
|
|
631
|
+
tags: content.tags,
|
|
632
|
+
lastOpened: lastOpened ?? "",
|
|
633
|
+
fileType: content.fileType,
|
|
634
|
+
type: "DocumentType",
|
|
635
|
+
};
|
|
636
|
+
}
|
|
648
637
|
}
|
|
649
638
|
/** list all items */
|
|
650
|
-
async listItems() {
|
|
651
|
-
const
|
|
652
|
-
|
|
653
|
-
throw Error("invalid entries");
|
|
654
|
-
return res;
|
|
639
|
+
async listItems(refresh = false) {
|
|
640
|
+
const ids = await this.listIds(refresh);
|
|
641
|
+
return await Promise.all(ids.map((id) => this.#convertEntry(id)));
|
|
655
642
|
}
|
|
656
643
|
async listIds(refresh = false) {
|
|
657
644
|
const [hash] = await this.#getRootHash(refresh);
|
|
@@ -713,45 +700,46 @@ class Remarkable {
|
|
|
713
700
|
}
|
|
714
701
|
const id = uuid4();
|
|
715
702
|
const now = new Date();
|
|
716
|
-
const
|
|
703
|
+
const metadata = {
|
|
704
|
+
parent,
|
|
705
|
+
pinned,
|
|
706
|
+
lastModified: (+now).toFixed(),
|
|
707
|
+
createdTime: (+now).toFixed(),
|
|
708
|
+
type: "DocumentType",
|
|
709
|
+
visibleName,
|
|
710
|
+
lastOpened: "0",
|
|
711
|
+
lastOpenedPage: 0,
|
|
712
|
+
};
|
|
713
|
+
const content = {
|
|
714
|
+
coverPageNumber,
|
|
715
|
+
documentMetadata: { authors, title, publicationDate, publisher },
|
|
716
|
+
extraMetadata,
|
|
717
|
+
lineHeight,
|
|
718
|
+
margins,
|
|
719
|
+
orientation,
|
|
720
|
+
fileType,
|
|
721
|
+
formatVersion: 1,
|
|
722
|
+
tags: tags?.map((name) => ({ name, timestamp: +now })) ?? [],
|
|
723
|
+
fontName,
|
|
724
|
+
textAlignment,
|
|
725
|
+
textScale,
|
|
726
|
+
zoomMode,
|
|
727
|
+
viewBackgroundFilter,
|
|
728
|
+
// NOTE for some reason we need to "fake" the number of pages at 1, and
|
|
729
|
+
// create "valid" output for that
|
|
730
|
+
originalPageCount: 1,
|
|
731
|
+
pageCount: 1,
|
|
732
|
+
pageTags: [],
|
|
733
|
+
pages: [uuid4()],
|
|
734
|
+
redirectionPageMap: [0],
|
|
735
|
+
sizeInBytes: buffer.length.toFixed(),
|
|
736
|
+
};
|
|
717
737
|
// upload raw files, and get root hash
|
|
718
738
|
const [[contentEntry, uploadContent], [metadataEntry, uploadMetadata], [pagedataEntry, uploadPagedata], [fileEntry, uploadFile], [rootHash, generation],] = await Promise.all([
|
|
719
|
-
this.raw.putContent(`${id}.content`,
|
|
720
|
-
|
|
721
|
-
documentMetadata: { authors, title, publicationDate, publisher },
|
|
722
|
-
extraMetadata,
|
|
723
|
-
lineHeight,
|
|
724
|
-
margins,
|
|
725
|
-
orientation,
|
|
726
|
-
fileType,
|
|
727
|
-
formatVersion: 1,
|
|
728
|
-
tags: tags?.map((name) => ({ name, timestamp: +now })) ?? [],
|
|
729
|
-
fontName,
|
|
730
|
-
textAlignment,
|
|
731
|
-
textScale,
|
|
732
|
-
zoomMode,
|
|
733
|
-
viewBackgroundFilter,
|
|
734
|
-
// NOTE for some reason we need to "fake" the number of pages at 1, and
|
|
735
|
-
// create "valid" output for that
|
|
736
|
-
originalPageCount: 1,
|
|
737
|
-
pageCount: 1,
|
|
738
|
-
pageTags: [],
|
|
739
|
-
pages: [uuid4()],
|
|
740
|
-
redirectionPageMap: [0],
|
|
741
|
-
sizeInBytes: buffer.length.toFixed(),
|
|
742
|
-
}),
|
|
743
|
-
this.raw.putMetadata(`${id}.metadata`, {
|
|
744
|
-
parent,
|
|
745
|
-
pinned,
|
|
746
|
-
lastModified: (+now).toFixed(),
|
|
747
|
-
createdTime: (+now).toFixed(),
|
|
748
|
-
type: "DocumentType",
|
|
749
|
-
visibleName,
|
|
750
|
-
lastOpened: "0",
|
|
751
|
-
lastOpenedPage: 0,
|
|
752
|
-
}),
|
|
739
|
+
this.raw.putContent(`${id}.content`, content),
|
|
740
|
+
this.raw.putMetadata(`${id}.metadata`, metadata),
|
|
753
741
|
// eslint-disable-next-line spellcheck/spell-checker
|
|
754
|
-
this.raw.
|
|
742
|
+
this.raw.putText(`${id}.pagedata`, "\n"),
|
|
755
743
|
this.raw.putFile(`${id}.${fileType}`, buffer),
|
|
756
744
|
this.#getRootHash(refresh),
|
|
757
745
|
]);
|
|
@@ -777,6 +765,10 @@ class Remarkable {
|
|
|
777
765
|
uploadCollection,
|
|
778
766
|
uploadRoot,
|
|
779
767
|
]);
|
|
768
|
+
// TODO we could return a full entry here, but we should probably decide
|
|
769
|
+
// what that should be, e.g. we could return more fields than the standard
|
|
770
|
+
// entry. Same for putFolder
|
|
771
|
+
// TODO we should also decide if the api should take hashes or ids...
|
|
780
772
|
await this.#putRootHash(rootEntry.hash, generation);
|
|
781
773
|
return { id, hash: collectionEntry.hash };
|
|
782
774
|
}
|
|
@@ -786,96 +778,134 @@ class Remarkable {
|
|
|
786
778
|
async putEpub(visibleName, buffer, opts = {}) {
|
|
787
779
|
return await this.#putFile(visibleName, "epub", buffer, opts);
|
|
788
780
|
}
|
|
789
|
-
/**
|
|
790
|
-
async
|
|
791
|
-
if (!idReg.test(parent)) {
|
|
781
|
+
/** create a folder */
|
|
782
|
+
async createFolder(visibleName, { parent = "" } = {}, refresh = false) {
|
|
783
|
+
if (parent && !idReg.test(parent)) {
|
|
792
784
|
throw new ValidationError(parent, idReg, "parent must be a valid document id");
|
|
793
785
|
}
|
|
794
|
-
const
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
786
|
+
const id = uuid4();
|
|
787
|
+
const now = new Date();
|
|
788
|
+
const content = {
|
|
789
|
+
tags: [],
|
|
790
|
+
};
|
|
791
|
+
const metadata = {
|
|
792
|
+
lastModified: (+now).toFixed(),
|
|
793
|
+
createdTime: (+now).toFixed(),
|
|
794
|
+
parent,
|
|
795
|
+
pinned: false,
|
|
796
|
+
type: "CollectionType",
|
|
797
|
+
visibleName,
|
|
798
|
+
};
|
|
799
|
+
// upload folder contents
|
|
800
|
+
const [[contentEntry, uploadContent], [metadataEntry, uploadMetadata], [rootHash, generation],] = await Promise.all([
|
|
801
|
+
this.raw.putContent(`${id}.content`, content),
|
|
802
|
+
this.raw.putMetadata(`${id}.metadata`, metadata),
|
|
803
|
+
this.#getRootHash(refresh),
|
|
804
|
+
]);
|
|
805
|
+
// now fetch root entries and upload this file entry
|
|
806
|
+
const [[collectionEntry, uploadCollection], rootEntries] = await Promise.all([
|
|
807
|
+
this.raw.putEntries(id, [contentEntry, metadataEntry]),
|
|
808
|
+
this.raw.getEntries(rootHash),
|
|
809
|
+
]);
|
|
810
|
+
// now upload a new root entry
|
|
811
|
+
rootEntries.push(collectionEntry);
|
|
812
|
+
const [rootEntry, uploadRoot] = await this.raw.putEntries("root", rootEntries);
|
|
813
|
+
// before updating the root hash, first upload everything
|
|
814
|
+
await Promise.all([
|
|
815
|
+
uploadContent,
|
|
816
|
+
uploadMetadata,
|
|
817
|
+
uploadCollection,
|
|
818
|
+
uploadRoot,
|
|
819
|
+
]);
|
|
820
|
+
// put root hash and return
|
|
821
|
+
await this.#putRootHash(rootEntry.hash, generation);
|
|
822
|
+
return { id, hash: collectionEntry.hash };
|
|
809
823
|
}
|
|
810
824
|
/** upload an epub */
|
|
811
|
-
async uploadEpub(visibleName, buffer,
|
|
812
|
-
return await this
|
|
825
|
+
async uploadEpub(visibleName, buffer, opts = {}) {
|
|
826
|
+
return await this.putEpub(visibleName, buffer, opts);
|
|
813
827
|
}
|
|
814
828
|
/** upload a pdf */
|
|
815
|
-
async uploadPdf(visibleName, buffer,
|
|
816
|
-
return await this
|
|
829
|
+
async uploadPdf(visibleName, buffer, opts = {}) {
|
|
830
|
+
return await this.putPdf(visibleName, buffer, opts);
|
|
817
831
|
}
|
|
818
|
-
async #
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
return
|
|
832
|
+
async #editMetaRaw(id, hash, update) {
|
|
833
|
+
const entries = await this.raw.getEntries(hash);
|
|
834
|
+
const metaInd = entries.findIndex((ent) => ent.id.endsWith(".metadata"));
|
|
835
|
+
const metaEntry = entries[metaInd];
|
|
836
|
+
if (metaEntry === undefined) {
|
|
837
|
+
throw new Error("internal error: couldn't find metadata in entry hash");
|
|
838
|
+
}
|
|
839
|
+
const meta = await this.raw.getMetadata(metaEntry.hash);
|
|
840
|
+
Object.assign(meta, update);
|
|
841
|
+
const [newMetaEntry, uploadMeta] = await this.raw.putMetadata(metaEntry.id, meta);
|
|
842
|
+
entries[metaInd] = newMetaEntry;
|
|
843
|
+
const [result, uploadentries] = await this.raw.putEntries(id, entries);
|
|
844
|
+
const upload = Promise.all([uploadMeta, uploadentries]).then(() => { });
|
|
845
|
+
return [result, upload];
|
|
846
|
+
}
|
|
847
|
+
async #editMeta(hash, update, refresh = false) {
|
|
848
|
+
const [rootHash, generation] = await this.#getRootHash(refresh);
|
|
849
|
+
const entries = await this.raw.getEntries(rootHash);
|
|
850
|
+
const hashInd = entries.findIndex((ent) => ent.hash === hash);
|
|
851
|
+
const hashEnt = entries[hashInd];
|
|
852
|
+
if (hashEnt === undefined) {
|
|
853
|
+
throw new HashNotFoundError(hash);
|
|
854
|
+
}
|
|
855
|
+
const [newEnt, uploadEnt] = await this.#editMetaRaw(hashEnt.id, hash, update);
|
|
856
|
+
entries[hashInd] = newEnt;
|
|
857
|
+
const [rootEntry, uploadRoot] = await this.raw.putEntries("root", entries);
|
|
858
|
+
await Promise.all([uploadEnt, uploadRoot]);
|
|
859
|
+
await this.#putRootHash(rootEntry.hash, generation);
|
|
860
|
+
return { hash: newEnt.hash };
|
|
832
861
|
}
|
|
833
862
|
/** move an entry */
|
|
834
|
-
async move(hash, parent) {
|
|
863
|
+
async move(hash, parent, refresh = false) {
|
|
835
864
|
if (!idReg.test(parent)) {
|
|
836
865
|
throw new ValidationError(parent, idReg, "parent must be a valid document id");
|
|
837
866
|
}
|
|
838
|
-
return await this.#
|
|
867
|
+
return await this.#editMeta(hash, { parent }, refresh);
|
|
839
868
|
}
|
|
840
869
|
/** delete an entry */
|
|
841
|
-
async delete(hash) {
|
|
842
|
-
return await this.move(hash, "trash");
|
|
870
|
+
async delete(hash, refresh = false) {
|
|
871
|
+
return await this.move(hash, "trash", refresh);
|
|
843
872
|
}
|
|
844
873
|
/** rename an entry */
|
|
845
|
-
async rename(hash, visibleName) {
|
|
846
|
-
return await this.#
|
|
847
|
-
}
|
|
848
|
-
/** bulk modify hashes */
|
|
849
|
-
async #bulkModify(hashes, properties) {
|
|
850
|
-
const invalidHashes = hashes.filter((hash) => !hashReg.test(hash));
|
|
851
|
-
if (invalidHashes.length) {
|
|
852
|
-
throw new ValidationError(hashes.join(", "), hashReg, "hashes to modify were not a valid hashes");
|
|
853
|
-
}
|
|
854
|
-
// this does not allow setting pinned, although I don't know why
|
|
855
|
-
const res = await this.#fileRequest({
|
|
856
|
-
body: JSON.stringify({
|
|
857
|
-
updates: properties,
|
|
858
|
-
hashes,
|
|
859
|
-
}),
|
|
860
|
-
method: "PATCH",
|
|
861
|
-
});
|
|
862
|
-
this.#lastHashGen = undefined; // clear the hash gen since this will change it
|
|
863
|
-
if (!hashesEntry.guardAssert(res))
|
|
864
|
-
throw Error("invalid hashes entry");
|
|
865
|
-
return res;
|
|
874
|
+
async rename(hash, visibleName, refresh = false) {
|
|
875
|
+
return await this.#editMeta(hash, { visibleName }, refresh);
|
|
866
876
|
}
|
|
867
877
|
/** move many hashes */
|
|
868
|
-
async bulkMove(hashes, parent) {
|
|
878
|
+
async bulkMove(hashes, parent, refresh = false) {
|
|
869
879
|
if (!idReg.test(parent)) {
|
|
870
880
|
throw new ValidationError(parent, idReg, "parent must be a valid document id");
|
|
871
881
|
}
|
|
872
|
-
|
|
882
|
+
const [rootHash, generation] = await this.#getRootHash(refresh);
|
|
883
|
+
const entries = await this.raw.getEntries(rootHash);
|
|
884
|
+
const hashSet = new Set(hashes);
|
|
885
|
+
const toUpdate = [];
|
|
886
|
+
const newEntries = [];
|
|
887
|
+
for (const entry of entries) {
|
|
888
|
+
const part = hashSet.has(entry.hash) ? toUpdate : newEntries;
|
|
889
|
+
part.push(entry);
|
|
890
|
+
}
|
|
891
|
+
const resolved = await Promise.all(toUpdate.map(({ id, hash }) => this.#editMetaRaw(id, hash, { parent })));
|
|
892
|
+
const uploads = [];
|
|
893
|
+
const result = {};
|
|
894
|
+
for (const [i, [newEnt, upload]] of resolved.entries()) {
|
|
895
|
+
newEntries.push(newEnt);
|
|
896
|
+
uploads.push(upload);
|
|
897
|
+
result[toUpdate[i].hash] = newEnt.hash;
|
|
898
|
+
}
|
|
899
|
+
const [rootEntry, uploadRoot] = await this.raw.putEntries("root", newEntries);
|
|
900
|
+
uploads.push(uploadRoot);
|
|
901
|
+
await Promise.all(uploads);
|
|
902
|
+
await this.#putRootHash(rootEntry.hash, generation);
|
|
903
|
+
return { hashes: result };
|
|
873
904
|
}
|
|
874
905
|
/** delete many hashes */
|
|
875
|
-
async bulkDelete(hashes) {
|
|
876
|
-
return await this.bulkMove(hashes, "trash");
|
|
906
|
+
async bulkDelete(hashes, refresh = false) {
|
|
907
|
+
return await this.bulkMove(hashes, "trash", refresh);
|
|
877
908
|
}
|
|
878
|
-
// TODO ostensibly we could implement a bulk rename but idk why
|
|
879
909
|
/** dump the raw cache */
|
|
880
910
|
dumpCache() {
|
|
881
911
|
return this.raw.dumpCache();
|
|
@@ -922,7 +952,7 @@ const cached = values(nullable(string()));
|
|
|
922
952
|
* registered. Create one with {@link register}.
|
|
923
953
|
* @returns an api instance
|
|
924
954
|
*/
|
|
925
|
-
export async function remarkable(deviceToken, { authHost = AUTH_HOST,
|
|
955
|
+
export async function remarkable(deviceToken, { authHost = AUTH_HOST, rawHost = RAW_HOST, cache, maxCacheSize = Infinity, } = {}) {
|
|
926
956
|
const resp = await fetch(`${authHost}/token/json/2/user/new`, {
|
|
927
957
|
method: "POST",
|
|
928
958
|
headers: {
|
|
@@ -939,7 +969,7 @@ export async function remarkable(deviceToken, { authHost = AUTH_HOST, syncHost =
|
|
|
939
969
|
const cache = maxCacheSize === Infinity
|
|
940
970
|
? new Map(entries)
|
|
941
971
|
: new LruCache(maxCacheSize, entries);
|
|
942
|
-
return new Remarkable(userToken,
|
|
972
|
+
return new Remarkable(userToken, rawHost, cache);
|
|
943
973
|
}
|
|
944
974
|
else {
|
|
945
975
|
throw new Error("cache was not a valid cache (json string mapping); your cache must be corrupted somehow. Either initialize remarkable without a cache, or fix its format.");
|