rmapi-js 10.1.1 → 11.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 +3 -3
- package/dist/error.d.ts +14 -0
- package/dist/error.js +21 -0
- package/dist/index.d.ts +3 -9
- package/dist/index.js +25 -26
- package/dist/lru.d.ts +8 -0
- package/dist/lru.js +64 -0
- package/dist/raw.d.ts +2 -3
- package/dist/raw.js +206 -200
- package/dist/rmapi-js.esm.min.js +9 -23
- package/dist/utils.d.ts +1 -0
- package/dist/utils.js +10 -0
- package/package.json +14 -9
package/README.md
CHANGED
|
@@ -35,7 +35,7 @@ const fileEntries = await api.listItems();
|
|
|
35
35
|
|
|
36
36
|
// In stateless environments, exchange once and reuse.
|
|
37
37
|
const sessionToken = await auth(token);
|
|
38
|
-
const
|
|
38
|
+
const sessionApi = session(sessionToken);
|
|
39
39
|
// cache `sessionToken` and reuse it across workers
|
|
40
40
|
```
|
|
41
41
|
|
|
@@ -62,8 +62,8 @@ Using these apis is a little riskier since they can potentially result in data l
|
|
|
62
62
|
// upload with custom line height not avilable through reMarkable
|
|
63
63
|
await api.putEpub("name", buffer, { lineHeight: 180 })
|
|
64
64
|
|
|
65
|
-
// fetch an uploaded
|
|
66
|
-
const buffer = await api.getEpub(hash)
|
|
65
|
+
// fetch an uploaded epub, using the id and hash (from listItems)
|
|
66
|
+
const buffer = await api.getEpub(id, hash)
|
|
67
67
|
```
|
|
68
68
|
|
|
69
69
|
### Gotchas
|
package/dist/error.d.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/** an error that results from a failed request */
|
|
2
|
+
export declare class ValidationError extends Error {
|
|
3
|
+
/** the response status number */
|
|
4
|
+
readonly field: string;
|
|
5
|
+
/** the response status text */
|
|
6
|
+
readonly regex: RegExp;
|
|
7
|
+
constructor(field: string, regex: RegExp, message: string);
|
|
8
|
+
}
|
|
9
|
+
/** an error that results while supplying a hash not found in the entries of the root hash */
|
|
10
|
+
export declare class HashNotFoundError extends Error {
|
|
11
|
+
/** the hash that couldn't be found */
|
|
12
|
+
readonly hash: string;
|
|
13
|
+
constructor(hash: string);
|
|
14
|
+
}
|
package/dist/error.js
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/** an error that results from a failed request */
|
|
2
|
+
export class ValidationError extends Error {
|
|
3
|
+
/** the response status number */
|
|
4
|
+
field;
|
|
5
|
+
/** the response status text */
|
|
6
|
+
regex;
|
|
7
|
+
constructor(field, regex, message) {
|
|
8
|
+
super(message);
|
|
9
|
+
this.field = field;
|
|
10
|
+
this.regex = regex;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
/** an error that results while supplying a hash not found in the entries of the root hash */
|
|
14
|
+
export class HashNotFoundError extends Error {
|
|
15
|
+
/** the hash that couldn't be found */
|
|
16
|
+
hash;
|
|
17
|
+
constructor(hash) {
|
|
18
|
+
super(`'${hash}' not found in the root hash`);
|
|
19
|
+
this.hash = hash;
|
|
20
|
+
}
|
|
21
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { type BackgroundFilter, type CollectionContent, type Content, type DocumentContent, type Metadata, type Orientation, type RawRemarkableApi, type SimpleEntry, type Tag, type TemplateContent, type TextAlignment, type ZoomMode } from "./raw";
|
|
2
|
-
export { HashNotFoundError, ValidationError } from "./error";
|
|
3
|
-
export type { BackgroundFilter, CollectionContent, Content, CPageNumberValue, CPagePage, CPageStringValue, CPages, CPageUUID, DocumentContent, DocumentMetadata, Entries, FileType, KeyboardMetadata, LegacyCollectionContent, LegacyDocumentContent, Metadata, Orientation, PageTag, RawEntry, RawRemarkableApi, SchemaVersion, SimpleEntry, Tag, TemplateContent, TextAlignment, UploadMimeType, ZoomMode, } from "./raw";
|
|
1
|
+
import { type BackgroundFilter, type CollectionContent, type Content, type DocumentContent, type Metadata, type Orientation, type RawRemarkableApi, type SimpleEntry, type Tag, type TemplateContent, type TextAlignment, type ZoomMode } from "./raw.js";
|
|
2
|
+
export { HashNotFoundError, ValidationError } from "./error.js";
|
|
3
|
+
export type { BackgroundFilter, CollectionContent, Content, CPageNumberValue, CPagePage, CPageStringValue, CPages, CPageUUID, DocumentContent, DocumentMetadata, Entries, FileType, KeyboardMetadata, LegacyCollectionContent, LegacyDocumentContent, Metadata, Orientation, PageTag, RawEntry, RawRemarkableApi, SchemaVersion, SimpleEntry, Tag, TemplateContent, TextAlignment, UploadMimeType, ZoomMode, } from "./raw.js";
|
|
4
4
|
/** common properties shared by collections and documents */
|
|
5
5
|
export interface EntryCommon {
|
|
6
6
|
/** the document id, a uuid4 */
|
|
@@ -498,12 +498,6 @@ export interface AuthOptions {
|
|
|
498
498
|
}
|
|
499
499
|
/** options for constructing an api instance from a session token */
|
|
500
500
|
export interface RemarkableSessionOptions {
|
|
501
|
-
/**
|
|
502
|
-
* the url for making synchronization requests
|
|
503
|
-
*
|
|
504
|
-
* @defaultValue "https://web.eu.tectonic.remarkable.com"
|
|
505
|
-
*/
|
|
506
|
-
syncHost?: string;
|
|
507
501
|
/**
|
|
508
502
|
* the base url for making upload requests
|
|
509
503
|
*
|
package/dist/index.js
CHANGED
|
@@ -17,9 +17,9 @@
|
|
|
17
17
|
* // persist token
|
|
18
18
|
* const api = await remarkable(token);
|
|
19
19
|
* // list all items (documents and collections)
|
|
20
|
-
* const [first, ...rest] = api.listItems();
|
|
20
|
+
* const [first, ...rest] = await api.listItems();
|
|
21
21
|
* // rename first item
|
|
22
|
-
* const entry = api.rename(first.hash, "new name");
|
|
22
|
+
* const entry = await api.rename(first.hash, "new name");
|
|
23
23
|
* ```
|
|
24
24
|
*
|
|
25
25
|
* @example
|
|
@@ -52,18 +52,15 @@
|
|
|
52
52
|
* @packageDocumentation
|
|
53
53
|
*/
|
|
54
54
|
import JSZip from "jszip";
|
|
55
|
-
import { nullable, string, values } from "jtd-ts";
|
|
56
55
|
import { v4 as uuid4 } from "uuid";
|
|
57
|
-
import {
|
|
58
|
-
import {
|
|
59
|
-
import {
|
|
60
|
-
|
|
56
|
+
import { z } from "zod";
|
|
57
|
+
import { HashNotFoundError, ValidationError } from "./error.js";
|
|
58
|
+
import { LruCache } from "./lru.js";
|
|
59
|
+
import { RawRemarkable, } from "./raw.js";
|
|
60
|
+
export { HashNotFoundError, ValidationError } from "./error.js";
|
|
61
61
|
const AUTH_HOST = "https://webapp-prod.cloud.remarkable.engineering";
|
|
62
62
|
const RAW_HOST = "https://eu.tectonic.remarkable.com";
|
|
63
63
|
const UPLOAD_HOST = "https://internal.cloud.remarkable.com";
|
|
64
|
-
// ------------ //
|
|
65
|
-
// Request Info //
|
|
66
|
-
// ------------ //
|
|
67
64
|
// The section has all the types that are stored in the remarkable cloud.
|
|
68
65
|
const idReg = /^([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}||trash)$/;
|
|
69
66
|
/** An error that gets thrown when the backend while trying to update
|
|
@@ -131,6 +128,7 @@ class Remarkable {
|
|
|
131
128
|
#cache;
|
|
132
129
|
raw;
|
|
133
130
|
#lastHashGen;
|
|
131
|
+
#schemaVersion;
|
|
134
132
|
constructor(sessionToken, rawHost, uploadHost, cache) {
|
|
135
133
|
this.#sessionToken = sessionToken;
|
|
136
134
|
this.#cache = cache;
|
|
@@ -138,15 +136,16 @@ class Remarkable {
|
|
|
138
136
|
}
|
|
139
137
|
async #getRootHash(refresh = false) {
|
|
140
138
|
if (refresh || this.#lastHashGen === undefined) {
|
|
141
|
-
|
|
139
|
+
const [hash, generation, schemaVersion] = await this.raw.getRootHash();
|
|
140
|
+
this.#lastHashGen = [hash, generation];
|
|
141
|
+
this.#schemaVersion = schemaVersion;
|
|
142
142
|
}
|
|
143
|
-
return this.#lastHashGen;
|
|
143
|
+
return [...this.#lastHashGen, this.#schemaVersion];
|
|
144
144
|
}
|
|
145
145
|
async #putRootHash(hash, generation) {
|
|
146
146
|
try {
|
|
147
147
|
const [rootHash, gen] = await this.raw.putRootHash(hash, generation);
|
|
148
|
-
|
|
149
|
-
this.#lastHashGen = [rootHash, gen, schemaVersion];
|
|
148
|
+
this.#lastHashGen = [rootHash, gen];
|
|
150
149
|
}
|
|
151
150
|
catch (ex) {
|
|
152
151
|
// if we hit a generation error, invalidate our cached generation
|
|
@@ -186,7 +185,7 @@ class Remarkable {
|
|
|
186
185
|
if (metaEnt === undefined) {
|
|
187
186
|
throw new Error(`couldn't find metadata for hash ${hash}`);
|
|
188
187
|
}
|
|
189
|
-
const [{ visibleName, lastModified, pinned, parent, lastOpened, new: isNew, source, }, content,] = await Promise.all([
|
|
188
|
+
const [{ visibleName, lastModified, pinned, parent, lastOpened, createdTime, new: isNew, source, }, content,] = await Promise.all([
|
|
190
189
|
this.raw.getMetadata(metaEnt.id, metaEnt.hash),
|
|
191
190
|
// collections don't always have content, since content only lists tags
|
|
192
191
|
contentEnt === undefined
|
|
@@ -203,6 +202,7 @@ class Remarkable {
|
|
|
203
202
|
pinned,
|
|
204
203
|
source,
|
|
205
204
|
parent,
|
|
205
|
+
createdTime,
|
|
206
206
|
type: "TemplateType",
|
|
207
207
|
};
|
|
208
208
|
}
|
|
@@ -336,7 +336,6 @@ class Remarkable {
|
|
|
336
336
|
const [[contentEntry, uploadContent], [metadataEntry, uploadMetadata], [pagedataEntry, uploadPagedata], [fileEntry, uploadFile], [rootHash, generation, schemaVersion],] = await Promise.all([
|
|
337
337
|
this.raw.putContent(`${id}.content`, content),
|
|
338
338
|
this.raw.putMetadata(`${id}.metadata`, metadata),
|
|
339
|
-
// eslint-disable-next-line spellcheck/spell-checker
|
|
340
339
|
this.raw.putText(`${id}.pagedata`, "\n"),
|
|
341
340
|
this.raw.putFile(`${id}.${fileType}`, buffer),
|
|
342
341
|
this.#getRootHash(refresh),
|
|
@@ -562,8 +561,9 @@ class Remarkable {
|
|
|
562
561
|
}
|
|
563
562
|
async pruneCache(refresh) {
|
|
564
563
|
const [rootHash] = await this.#getRootHash(refresh);
|
|
565
|
-
//
|
|
564
|
+
// start by assuming every cached hash is unreachable, then keep the ones we reach
|
|
566
565
|
const toDelete = new Set(this.#cache.keys());
|
|
566
|
+
toDelete.delete(rootHash);
|
|
567
567
|
// bfs through entries (to semi-optimize promise waiting, although this
|
|
568
568
|
// should only go one step) to track all hashes encountered
|
|
569
569
|
// NOTE that we could increase the cache in this process, or it's possible
|
|
@@ -573,9 +573,9 @@ class Remarkable {
|
|
|
573
573
|
let nextEntries = [];
|
|
574
574
|
while (entries.length) {
|
|
575
575
|
for (const entryList of entries) {
|
|
576
|
-
for (const { hash,
|
|
577
|
-
toDelete.
|
|
578
|
-
if (
|
|
576
|
+
for (const { hash, subfiles, id } of entryList) {
|
|
577
|
+
toDelete.delete(hash);
|
|
578
|
+
if (subfiles > 0) {
|
|
579
579
|
nextEntries.push(this.raw.getEntries(`${id}.docSchema`, hash));
|
|
580
580
|
}
|
|
581
581
|
}
|
|
@@ -588,12 +588,11 @@ class Remarkable {
|
|
|
588
588
|
this.#cache.delete(key);
|
|
589
589
|
}
|
|
590
590
|
}
|
|
591
|
-
// finally remove any values we had in the cache initially, but couldn't reach
|
|
592
591
|
clearCache() {
|
|
593
592
|
this.raw.clearCache();
|
|
594
593
|
}
|
|
595
594
|
}
|
|
596
|
-
const cached =
|
|
595
|
+
const cached = z.record(z.string(), z.string().nullable());
|
|
597
596
|
/**
|
|
598
597
|
* Exchange a device token for a session token.
|
|
599
598
|
*
|
|
@@ -624,8 +623,9 @@ export async function auth(deviceToken, { authHost = AUTH_HOST } = {}) {
|
|
|
624
623
|
*/
|
|
625
624
|
export function session(sessionToken, { rawHost = RAW_HOST, uploadHost = UPLOAD_HOST, cache, maxCacheSize = Infinity, } = {}) {
|
|
626
625
|
const initCache = JSON.parse(cache ?? "{}");
|
|
627
|
-
|
|
628
|
-
|
|
626
|
+
const parsedCache = cached.safeParse(initCache);
|
|
627
|
+
if (parsedCache.success) {
|
|
628
|
+
const entries = Object.entries(parsedCache.data);
|
|
629
629
|
const cacheMap = maxCacheSize === Infinity
|
|
630
630
|
? new Map(entries)
|
|
631
631
|
: new LruCache(maxCacheSize, entries);
|
|
@@ -644,13 +644,12 @@ export function session(sessionToken, { rawHost = RAW_HOST, uploadHost = UPLOAD_
|
|
|
644
644
|
* @returns an api instance
|
|
645
645
|
*/
|
|
646
646
|
export async function remarkable(deviceToken, options = {}) {
|
|
647
|
-
const { authHost, rawHost, uploadHost, cache, maxCacheSize
|
|
647
|
+
const { authHost, rawHost, uploadHost, cache, maxCacheSize } = options ?? {};
|
|
648
648
|
const sessionToken = await auth(deviceToken, { authHost });
|
|
649
649
|
return session(sessionToken, {
|
|
650
650
|
rawHost,
|
|
651
651
|
uploadHost,
|
|
652
652
|
cache,
|
|
653
653
|
maxCacheSize,
|
|
654
|
-
syncHost,
|
|
655
654
|
});
|
|
656
655
|
}
|
package/dist/lru.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export declare class LruCache extends Map<string, string | null> {
|
|
2
|
+
#private;
|
|
3
|
+
constructor(maxSize: number, entries?: Iterable<[string, string | null]>);
|
|
4
|
+
get(key: string): string | null | undefined;
|
|
5
|
+
set(key: string, value: string | null): this;
|
|
6
|
+
delete(key: string): boolean;
|
|
7
|
+
clear(): void;
|
|
8
|
+
}
|
package/dist/lru.js
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
export class LruCache extends Map {
|
|
2
|
+
#maxSize;
|
|
3
|
+
#currentSize = 0;
|
|
4
|
+
constructor(maxSize, entries = []) {
|
|
5
|
+
super();
|
|
6
|
+
this.#maxSize = maxSize;
|
|
7
|
+
for (const [key, value] of entries) {
|
|
8
|
+
this.set(key, value);
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
get(key) {
|
|
12
|
+
const res = super.get(key);
|
|
13
|
+
if (res !== undefined) {
|
|
14
|
+
// update order so key is most recent
|
|
15
|
+
super.delete(key);
|
|
16
|
+
super.set(key, res);
|
|
17
|
+
}
|
|
18
|
+
return res;
|
|
19
|
+
}
|
|
20
|
+
set(key, value) {
|
|
21
|
+
const existing = super.get(key);
|
|
22
|
+
if (existing === undefined) {
|
|
23
|
+
this.#currentSize += key.length; // adding a new key
|
|
24
|
+
}
|
|
25
|
+
else if (existing !== null) {
|
|
26
|
+
this.#currentSize -= existing.length; // removing old value
|
|
27
|
+
}
|
|
28
|
+
if (value !== null) {
|
|
29
|
+
this.#currentSize += value.length;
|
|
30
|
+
}
|
|
31
|
+
// delete existing value
|
|
32
|
+
super.delete(key);
|
|
33
|
+
// evict down to desired size
|
|
34
|
+
let entry;
|
|
35
|
+
while (this.#currentSize > this.#maxSize &&
|
|
36
|
+
(entry = this.entries().next().value)) {
|
|
37
|
+
const [oldestKey, oldestValue] = entry;
|
|
38
|
+
super.delete(oldestKey);
|
|
39
|
+
this.#currentSize -= oldestKey.length;
|
|
40
|
+
if (oldestValue !== null) {
|
|
41
|
+
this.#currentSize -= oldestValue.length;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
// finally insert new key and return
|
|
45
|
+
super.set(key, value);
|
|
46
|
+
return this;
|
|
47
|
+
}
|
|
48
|
+
delete(key) {
|
|
49
|
+
const value = super.get(key);
|
|
50
|
+
if (value === undefined) {
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
super.delete(key);
|
|
54
|
+
if (value !== null) {
|
|
55
|
+
this.#currentSize -= value.length;
|
|
56
|
+
}
|
|
57
|
+
this.#currentSize -= key.length;
|
|
58
|
+
return true;
|
|
59
|
+
}
|
|
60
|
+
clear() {
|
|
61
|
+
super.clear();
|
|
62
|
+
this.#currentSize = 0;
|
|
63
|
+
}
|
|
64
|
+
}
|
package/dist/raw.d.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import "core-js/proposals/array-buffer-base64";
|
|
2
1
|
/** request types */
|
|
3
2
|
export type RequestMethod = "POST" | "GET" | "PUT" | "DELETE" | "PATCH" | "OPTIONS";
|
|
4
3
|
/** the supported upload mime types */
|
|
@@ -560,7 +559,7 @@ export interface RawRemarkableApi {
|
|
|
560
559
|
*
|
|
561
560
|
* @returns the new list entry and a promise to finish the upload
|
|
562
561
|
*/
|
|
563
|
-
putEntries(id: string, entries: RawEntry[], schemaVersion: SchemaVersion): Promise<[RawEntry, Promise<void>]>;
|
|
562
|
+
putEntries(id: string, entries: readonly RawEntry[], schemaVersion: SchemaVersion): Promise<[RawEntry, Promise<void>]>;
|
|
564
563
|
/**
|
|
565
564
|
* upload a file to the reMarkable cloud using the simple api
|
|
566
565
|
*
|
|
@@ -603,7 +602,7 @@ export declare class RawRemarkable implements RawRemarkableApi {
|
|
|
603
602
|
putText(id: string, text: string): Promise<[RawEntry, Promise<void>]>;
|
|
604
603
|
putContent(id: string, content: Content): Promise<[RawEntry, Promise<void>]>;
|
|
605
604
|
putMetadata(id: string, metadata: Metadata): Promise<[RawEntry, Promise<void>]>;
|
|
606
|
-
putEntries(id: string, entries: RawEntry[], schemaVersion: SchemaVersion): Promise<[RawEntry, Promise<void>]>;
|
|
605
|
+
putEntries(id: string, entries: readonly RawEntry[], schemaVersion: SchemaVersion): Promise<[RawEntry, Promise<void>]>;
|
|
607
606
|
uploadFile(visibleName: string, bytes: Uint8Array, mime: UploadMimeType): Promise<SimpleEntry>;
|
|
608
607
|
dumpCache(): string;
|
|
609
608
|
clearCache(): void;
|