rmapi-js 8.3.0 → 8.5.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 +13 -4
- package/dist/index.d.ts +49 -24
- package/dist/index.js +60 -27
- package/dist/raw.d.ts +33 -10
- package/dist/raw.js +36 -11
- package/dist/rmapi-js.esm.min.js +13 -18
- package/package.json +9 -17
package/README.md
CHANGED
|
@@ -21,19 +21,28 @@ and folders ("collections"). The hash indicates the full current state to manage
|
|
|
21
21
|
## Usage
|
|
22
22
|
|
|
23
23
|
To explore files in the cloud, you need to first register your api and persist
|
|
24
|
-
the token. Then you can use `
|
|
24
|
+
the token. Then you can use `listItems` to explore entries of different file
|
|
25
25
|
collections.
|
|
26
26
|
|
|
27
27
|
```ts
|
|
28
|
-
import { register, remarkable } from "rmapi-js";
|
|
28
|
+
import { auth, register, remarkable, session } from "rmapi-js";
|
|
29
29
|
|
|
30
30
|
const code = "..."; // eight letter code from https://my.remarkable.com/device/desktop/connect
|
|
31
31
|
const token = await register(code);
|
|
32
32
|
// persist token so you don't have to register again
|
|
33
33
|
const api = await remarkable(token);
|
|
34
|
-
const fileEntries = await api.
|
|
34
|
+
const fileEntries = await api.listItems();
|
|
35
|
+
|
|
36
|
+
// In stateless environments, exchange once and reuse.
|
|
37
|
+
const sessionToken = await auth(token);
|
|
38
|
+
const api = session(sessionToken);
|
|
39
|
+
// cache `sessionToken` and reuse it across workers
|
|
35
40
|
```
|
|
36
41
|
|
|
42
|
+
`auth` performs the same network call that `remarkable` does for you internally,
|
|
43
|
+
returning a short-lived session token. `session` is synchronous,
|
|
44
|
+
letting you construct clients from cached tokens without making a network call.
|
|
45
|
+
|
|
37
46
|
To upload an epub or pdf, simply call upload with the appropriate name and buffer.
|
|
38
47
|
|
|
39
48
|
```ts
|
|
@@ -53,7 +62,7 @@ Using these apis is a little riskier since they can potentially result in data l
|
|
|
53
62
|
// upload with custom line height not avilable through reMarkable
|
|
54
63
|
await api.putEpub("name", buffer, { lineHeight: 180 })
|
|
55
64
|
|
|
56
|
-
// fetch an uploaded pdf, using the hash (from
|
|
65
|
+
// fetch an uploaded pdf, using the hash (from listItems)
|
|
57
66
|
const buffer = await api.getEpub(hash)
|
|
58
67
|
```
|
|
59
68
|
|
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 Tag, type TemplateContent, type TextAlignment, type 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";
|
|
2
2
|
export { HashNotFoundError, ValidationError } from "./error";
|
|
3
|
-
export type { BackgroundFilter, CollectionContent, Content, CPageNumberValue, CPagePage,
|
|
3
|
+
export type { BackgroundFilter, CollectionContent, Content, CPageNumberValue, CPagePage, CPageStringValue, CPages, CPageUUID, DocumentContent, DocumentMetadata, FileType, KeyboardMetadata, Metadata, Orientation, PageTag, RawEntry, RawFileEntry, RawListEntry, RawRemarkableApi, SimpleEntry, Tag, TemplateContent, TextAlignment, UploadMimeType, ZoomMode, } from "./raw";
|
|
4
4
|
/** common properties shared by collections and documents */
|
|
5
5
|
export interface EntryCommon {
|
|
6
6
|
/** the document id, a uuid4 */
|
|
@@ -50,13 +50,6 @@ export interface TemplateType extends EntryCommon {
|
|
|
50
50
|
}
|
|
51
51
|
/** a remarkable entry for cloud items */
|
|
52
52
|
export type Entry = CollectionEntry | DocumentType | TemplateType;
|
|
53
|
-
/** an simple entry without any extra information */
|
|
54
|
-
export interface SimpleEntry {
|
|
55
|
-
/** the document id */
|
|
56
|
-
id: string;
|
|
57
|
-
/** the document hash */
|
|
58
|
-
hash: string;
|
|
59
|
-
}
|
|
60
53
|
/** the new hash of a modified entry */
|
|
61
54
|
export interface HashEntry {
|
|
62
55
|
/** the actual hash */
|
|
@@ -67,6 +60,11 @@ export interface HashesEntry {
|
|
|
67
60
|
/** the mapping from old to new hashes */
|
|
68
61
|
hashes: Record<string, string>;
|
|
69
62
|
}
|
|
63
|
+
/** options for creating a folder */
|
|
64
|
+
export interface FolderOptions {
|
|
65
|
+
/** the id of the folder's parent directory, "" or omitted for root */
|
|
66
|
+
parent?: string;
|
|
67
|
+
}
|
|
70
68
|
/** An error that gets thrown when the backend while trying to update
|
|
71
69
|
*
|
|
72
70
|
* IF you encounter this error, you likely just need to try th request again. If
|
|
@@ -114,11 +112,6 @@ export interface RegisterOptions {
|
|
|
114
112
|
* @returns the device token necessary for creating an api instace. These never expire so persist as long as necessary.
|
|
115
113
|
*/
|
|
116
114
|
export declare function register(code: string, { deviceDesc, uuid, authHost, }?: RegisterOptions): Promise<string>;
|
|
117
|
-
/** options available when uploading a document */
|
|
118
|
-
export interface UploadOptions {
|
|
119
|
-
/** an optional parent id to set when uploading */
|
|
120
|
-
parent?: string;
|
|
121
|
-
}
|
|
122
115
|
/**
|
|
123
116
|
* options for putting a file onto reMarkable
|
|
124
117
|
*
|
|
@@ -322,7 +315,7 @@ export interface RemarkableApi {
|
|
|
322
315
|
*/
|
|
323
316
|
putEpub(visibleName: string, buffer: Uint8Array, opts?: PutOptions): Promise<SimpleEntry>;
|
|
324
317
|
/** create a folder */
|
|
325
|
-
|
|
318
|
+
putFolder(visibleName: string, opts?: FolderOptions, refresh?: boolean): Promise<SimpleEntry>;
|
|
326
319
|
/**
|
|
327
320
|
* upload an epub
|
|
328
321
|
*
|
|
@@ -332,12 +325,12 @@ export interface RemarkableApi {
|
|
|
332
325
|
* ```
|
|
333
326
|
*
|
|
334
327
|
* @remarks
|
|
335
|
-
* this
|
|
328
|
+
* this uses a simpler api that works even with schema version 4.
|
|
336
329
|
*
|
|
337
330
|
* @param visibleName - the name to show for the uploaded epub
|
|
338
331
|
* @param buffer - the epub contents
|
|
339
332
|
*/
|
|
340
|
-
uploadEpub(visibleName: string, buffer: Uint8Array
|
|
333
|
+
uploadEpub(visibleName: string, buffer: Uint8Array): Promise<SimpleEntry>;
|
|
341
334
|
/**
|
|
342
335
|
* upload a pdf
|
|
343
336
|
*
|
|
@@ -347,12 +340,14 @@ export interface RemarkableApi {
|
|
|
347
340
|
* ```
|
|
348
341
|
*
|
|
349
342
|
* @remarks
|
|
350
|
-
* this
|
|
343
|
+
* this uses a simpler api that works even with schema version 4.
|
|
351
344
|
*
|
|
352
345
|
* @param visibleName - the name to show for the uploaded epub
|
|
353
346
|
* @param buffer - the epub contents
|
|
354
347
|
*/
|
|
355
|
-
uploadPdf(visibleName: string, buffer: Uint8Array
|
|
348
|
+
uploadPdf(visibleName: string, buffer: Uint8Array): Promise<SimpleEntry>;
|
|
349
|
+
/** create a folder using the simple api */
|
|
350
|
+
uploadFolder(visibleName: string): Promise<SimpleEntry>;
|
|
356
351
|
/**
|
|
357
352
|
* update content metadata for a document
|
|
358
353
|
*
|
|
@@ -487,20 +482,29 @@ export interface RemarkableApi {
|
|
|
487
482
|
*/
|
|
488
483
|
clearCache(): void;
|
|
489
484
|
}
|
|
490
|
-
/**
|
|
491
|
-
export interface
|
|
485
|
+
/** configuration for exchanging a device token */
|
|
486
|
+
export interface AuthOptions {
|
|
492
487
|
/**
|
|
493
488
|
* the url for making authorization requests
|
|
494
489
|
*
|
|
495
490
|
* @defaultValue "https://webapp-prod.cloud.remarkable.engineering"
|
|
496
491
|
*/
|
|
497
492
|
authHost?: string;
|
|
493
|
+
}
|
|
494
|
+
/** options for constructing an api instance from a session token */
|
|
495
|
+
export interface RemarkableSessionOptions {
|
|
498
496
|
/**
|
|
499
497
|
* the url for making synchronization requests
|
|
500
498
|
*
|
|
501
499
|
* @defaultValue "https://web.eu.tectonic.remarkable.com"
|
|
502
500
|
*/
|
|
503
501
|
syncHost?: string;
|
|
502
|
+
/**
|
|
503
|
+
* the base url for making upload requests
|
|
504
|
+
*
|
|
505
|
+
* @defaultValue "https://internal.cloud.remarkable.com"
|
|
506
|
+
*/
|
|
507
|
+
uploadHost?: string;
|
|
504
508
|
/**
|
|
505
509
|
* the url for making requests using the low-level api
|
|
506
510
|
*
|
|
@@ -525,14 +529,35 @@ export interface RemarkableOptions {
|
|
|
525
529
|
*/
|
|
526
530
|
maxCacheSize?: number;
|
|
527
531
|
}
|
|
532
|
+
/** options for a remarkable instance */
|
|
533
|
+
export interface RemarkableOptions extends AuthOptions, RemarkableSessionOptions {
|
|
534
|
+
}
|
|
535
|
+
/**
|
|
536
|
+
* Exchange a device token for a session token.
|
|
537
|
+
*
|
|
538
|
+
* @param deviceToken - the device token proving this api instance is
|
|
539
|
+
* registered. Create one with {@link register}.
|
|
540
|
+
* @returns the session token returned by the reMarkable service
|
|
541
|
+
*/
|
|
542
|
+
export declare function auth(deviceToken: string, { authHost }?: AuthOptions): Promise<string>;
|
|
543
|
+
/**
|
|
544
|
+
* Create an API instance from an existing session token.
|
|
545
|
+
*
|
|
546
|
+
* If requests start failing, simply recreate the api instance with a freshly
|
|
547
|
+
* fetched session token.
|
|
548
|
+
*
|
|
549
|
+
* @param sessionToken - the session token used for authorization
|
|
550
|
+
* @returns an api instance
|
|
551
|
+
*/
|
|
552
|
+
export declare function session(sessionToken: string, { rawHost, uploadHost, cache, maxCacheSize, }?: RemarkableSessionOptions): RemarkableApi;
|
|
528
553
|
/**
|
|
529
554
|
* create an instance of the api
|
|
530
555
|
*
|
|
531
|
-
* This gets a temporary authentication token with the device token
|
|
532
|
-
*
|
|
556
|
+
* This gets a temporary authentication token with the device token and then
|
|
557
|
+
* constructs the api instance.
|
|
533
558
|
*
|
|
534
559
|
* @param deviceToken - the device token proving this api instance is
|
|
535
560
|
* registered. Create one with {@link register}.
|
|
536
561
|
* @returns an api instance
|
|
537
562
|
*/
|
|
538
|
-
export declare function remarkable(deviceToken: string,
|
|
563
|
+
export declare function remarkable(deviceToken: string, options?: RemarkableOptions): Promise<RemarkableApi>;
|
package/dist/index.js
CHANGED
|
@@ -60,6 +60,7 @@ import { RawRemarkable, } from "./raw";
|
|
|
60
60
|
export { HashNotFoundError, ValidationError } from "./error";
|
|
61
61
|
const AUTH_HOST = "https://webapp-prod.cloud.remarkable.engineering";
|
|
62
62
|
const RAW_HOST = "https://eu.tectonic.remarkable.com";
|
|
63
|
+
const UPLOAD_HOST = "https://internal.cloud.remarkable.com";
|
|
63
64
|
// ------------ //
|
|
64
65
|
// Request Info //
|
|
65
66
|
// ------------ //
|
|
@@ -125,15 +126,15 @@ export async function register(code, { deviceDesc = "browser-chrome", uuid = uui
|
|
|
125
126
|
}
|
|
126
127
|
/** the implementation of that api */
|
|
127
128
|
class Remarkable {
|
|
128
|
-
#
|
|
129
|
+
#sessionToken;
|
|
129
130
|
/** the same cache that underlies the raw api, allowing us to modify it */
|
|
130
131
|
#cache;
|
|
131
132
|
raw;
|
|
132
133
|
#lastHashGen;
|
|
133
|
-
constructor(
|
|
134
|
-
this.#
|
|
134
|
+
constructor(sessionToken, rawHost, uploadHost, cache) {
|
|
135
|
+
this.#sessionToken = sessionToken;
|
|
135
136
|
this.#cache = cache;
|
|
136
|
-
this.raw = new RawRemarkable((method, url, { body, headers } = {}) => this.#authedFetch(url, { method, body, headers }), cache, rawHost);
|
|
137
|
+
this.raw = new RawRemarkable((method, url, { body, headers } = {}) => this.#authedFetch(url, { method, body, headers }), cache, rawHost, uploadHost);
|
|
137
138
|
}
|
|
138
139
|
async #getRootHash(refresh = false) {
|
|
139
140
|
if (refresh || this.#lastHashGen === undefined) {
|
|
@@ -157,10 +158,11 @@ class Remarkable {
|
|
|
157
158
|
const resp = await fetch(url, {
|
|
158
159
|
method,
|
|
159
160
|
headers: {
|
|
160
|
-
Authorization: `Bearer ${this.#
|
|
161
|
+
Authorization: `Bearer ${this.#sessionToken}`,
|
|
161
162
|
...headers,
|
|
162
163
|
},
|
|
163
|
-
|
|
164
|
+
// fetch works correctly with uint8 arrays, but is not hinted correctly
|
|
165
|
+
body: body,
|
|
164
166
|
});
|
|
165
167
|
if (!resp.ok) {
|
|
166
168
|
const msg = await resp.text();
|
|
@@ -182,12 +184,12 @@ class Remarkable {
|
|
|
182
184
|
if (metaEnt === undefined) {
|
|
183
185
|
throw new Error(`couldn't find metadata for hash ${hash}`);
|
|
184
186
|
}
|
|
185
|
-
else if (contentEnt === undefined) {
|
|
186
|
-
throw new Error(`couldn't find content for hash ${hash}`);
|
|
187
|
-
}
|
|
188
187
|
const [{ visibleName, lastModified, pinned, parent, lastOpened, new: isNew, source, }, content,] = await Promise.all([
|
|
189
188
|
this.raw.getMetadata(metaEnt.hash),
|
|
190
|
-
|
|
189
|
+
// collections don't always have content, since content only lists tags
|
|
190
|
+
contentEnt === undefined
|
|
191
|
+
? Promise.resolve({ fileType: undefined, tags: undefined })
|
|
192
|
+
: this.raw.getContent(contentEnt.hash),
|
|
191
193
|
]);
|
|
192
194
|
if ("templateVersion" in content) {
|
|
193
195
|
return {
|
|
@@ -373,7 +375,7 @@ class Remarkable {
|
|
|
373
375
|
return await this.#putFile(visibleName, "epub", buffer, opts);
|
|
374
376
|
}
|
|
375
377
|
/** create a folder */
|
|
376
|
-
async
|
|
378
|
+
async putFolder(visibleName, { parent = "" } = {}, refresh = false) {
|
|
377
379
|
if (parent && !idReg.test(parent)) {
|
|
378
380
|
throw new ValidationError(parent, idReg, "parent must be a valid document id");
|
|
379
381
|
}
|
|
@@ -416,12 +418,16 @@ class Remarkable {
|
|
|
416
418
|
return { id, hash: collectionEntry.hash };
|
|
417
419
|
}
|
|
418
420
|
/** upload an epub */
|
|
419
|
-
async uploadEpub(visibleName, buffer
|
|
420
|
-
return await this.
|
|
421
|
+
async uploadEpub(visibleName, buffer) {
|
|
422
|
+
return await this.raw.uploadFile(visibleName, buffer, "application/epub+zip");
|
|
421
423
|
}
|
|
422
424
|
/** upload a pdf */
|
|
423
|
-
async uploadPdf(visibleName, buffer
|
|
424
|
-
return await this.
|
|
425
|
+
async uploadPdf(visibleName, buffer) {
|
|
426
|
+
return await this.raw.uploadFile(visibleName, buffer, "application/pdf");
|
|
427
|
+
}
|
|
428
|
+
/** upload a folder */
|
|
429
|
+
async uploadFolder(visibleName) {
|
|
430
|
+
return await this.raw.uploadFile(visibleName, new Uint8Array(0), "folder");
|
|
425
431
|
}
|
|
426
432
|
/** edit just a content entry */
|
|
427
433
|
async #editContentRaw(id, hash, update) {
|
|
@@ -590,16 +596,13 @@ class Remarkable {
|
|
|
590
596
|
}
|
|
591
597
|
const cached = values(nullable(string()));
|
|
592
598
|
/**
|
|
593
|
-
*
|
|
594
|
-
*
|
|
595
|
-
* This gets a temporary authentication token with the device token. If
|
|
596
|
-
* requests start failing, simply recreate the api instance.
|
|
599
|
+
* Exchange a device token for a session token.
|
|
597
600
|
*
|
|
598
601
|
* @param deviceToken - the device token proving this api instance is
|
|
599
602
|
* registered. Create one with {@link register}.
|
|
600
|
-
* @returns
|
|
603
|
+
* @returns the session token returned by the reMarkable service
|
|
601
604
|
*/
|
|
602
|
-
export async function
|
|
605
|
+
export async function auth(deviceToken, { authHost = AUTH_HOST } = {}) {
|
|
603
606
|
const resp = await fetch(`${authHost}/token/json/2/user/new`, {
|
|
604
607
|
method: "POST",
|
|
605
608
|
headers: {
|
|
@@ -609,16 +612,46 @@ export async function remarkable(deviceToken, { authHost = AUTH_HOST, rawHost =
|
|
|
609
612
|
if (!resp.ok) {
|
|
610
613
|
throw new Error(`couldn't fetch auth token: ${resp.statusText}`);
|
|
611
614
|
}
|
|
612
|
-
|
|
615
|
+
return await resp.text();
|
|
616
|
+
}
|
|
617
|
+
/**
|
|
618
|
+
* Create an API instance from an existing session token.
|
|
619
|
+
*
|
|
620
|
+
* If requests start failing, simply recreate the api instance with a freshly
|
|
621
|
+
* fetched session token.
|
|
622
|
+
*
|
|
623
|
+
* @param sessionToken - the session token used for authorization
|
|
624
|
+
* @returns an api instance
|
|
625
|
+
*/
|
|
626
|
+
export function session(sessionToken, { rawHost = RAW_HOST, uploadHost = UPLOAD_HOST, cache, maxCacheSize = Infinity, } = {}) {
|
|
613
627
|
const initCache = JSON.parse(cache ?? "{}");
|
|
614
628
|
if (cached.guard(initCache)) {
|
|
615
629
|
const entries = Object.entries(initCache);
|
|
616
|
-
const
|
|
630
|
+
const cacheMap = maxCacheSize === Infinity
|
|
617
631
|
? new Map(entries)
|
|
618
632
|
: new LruCache(maxCacheSize, entries);
|
|
619
|
-
return new Remarkable(
|
|
620
|
-
}
|
|
621
|
-
else {
|
|
622
|
-
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.");
|
|
633
|
+
return new Remarkable(sessionToken, rawHost, uploadHost, cacheMap);
|
|
623
634
|
}
|
|
635
|
+
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.");
|
|
636
|
+
}
|
|
637
|
+
/**
|
|
638
|
+
* create an instance of the api
|
|
639
|
+
*
|
|
640
|
+
* This gets a temporary authentication token with the device token and then
|
|
641
|
+
* constructs the api instance.
|
|
642
|
+
*
|
|
643
|
+
* @param deviceToken - the device token proving this api instance is
|
|
644
|
+
* registered. Create one with {@link register}.
|
|
645
|
+
* @returns an api instance
|
|
646
|
+
*/
|
|
647
|
+
export async function remarkable(deviceToken, options = {}) {
|
|
648
|
+
const { authHost, rawHost, uploadHost, cache, maxCacheSize, syncHost } = options ?? {};
|
|
649
|
+
const sessionToken = await auth(deviceToken, { authHost });
|
|
650
|
+
return session(sessionToken, {
|
|
651
|
+
rawHost,
|
|
652
|
+
uploadHost,
|
|
653
|
+
cache,
|
|
654
|
+
maxCacheSize,
|
|
655
|
+
syncHost,
|
|
656
|
+
});
|
|
624
657
|
}
|
package/dist/raw.d.ts
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
/** request types */
|
|
2
2
|
export type RequestMethod = "POST" | "GET" | "PUT" | "DELETE" | "PATCH" | "OPTIONS";
|
|
3
|
+
/** the supported upload mime types */
|
|
4
|
+
export type UploadMimeType = "application/pdf" | "application/epub+zip" | "folder";
|
|
5
|
+
/** an simple entry without any extra information */
|
|
6
|
+
export interface SimpleEntry {
|
|
7
|
+
/** the document id */
|
|
8
|
+
id: string;
|
|
9
|
+
/** the document hash */
|
|
10
|
+
hash: string;
|
|
11
|
+
}
|
|
3
12
|
/**
|
|
4
13
|
* the low-level entry corresponding to a collection of files
|
|
5
14
|
*
|
|
@@ -52,7 +61,7 @@ export interface PageTag extends Tag {
|
|
|
52
61
|
/** all supported document orientations */
|
|
53
62
|
export type Orientation = "portrait" | "landscape";
|
|
54
63
|
/** all supported text alignments */
|
|
55
|
-
export type TextAlignment = "justify" | "left";
|
|
64
|
+
export type TextAlignment = "" | "justify" | "left";
|
|
56
65
|
/** types of zoom modes for documents, applies primarily to pdf files */
|
|
57
66
|
export type ZoomMode = "bestFit" | "customFit" | "fitToHeight" | "fitToWidth";
|
|
58
67
|
/**
|
|
@@ -193,8 +202,8 @@ export interface DocumentContent {
|
|
|
193
202
|
pageCount: number;
|
|
194
203
|
/** the page tags for the document */
|
|
195
204
|
pageTags?: PageTag[];
|
|
196
|
-
/** a list of the ids of each page in the document */
|
|
197
|
-
pages?: string[];
|
|
205
|
+
/** a list of the ids of each page in the document, or null when never opened */
|
|
206
|
+
pages?: string[] | null;
|
|
198
207
|
/** a mapping from page number to page id in pages */
|
|
199
208
|
redirectionPageMap?: number[];
|
|
200
209
|
/** ostensibly the size in bytes of the file, but this differs from other measurements */
|
|
@@ -518,9 +527,24 @@ export interface RawRemarkableApi {
|
|
|
518
527
|
* @param id - the id of the list to upload - this should be the item id if
|
|
519
528
|
* uploading an item list, or "root" if uploading a new root list.
|
|
520
529
|
* @param entries - the entries to upload
|
|
530
|
+
*
|
|
521
531
|
* @returns the new list entry and a promise to finish the upload
|
|
522
532
|
*/
|
|
523
533
|
putEntries(id: string, entries: RawEntry[]): Promise<[RawListEntry, Promise<void>]>;
|
|
534
|
+
/**
|
|
535
|
+
* upload a file to the reMarkable cloud using the simple api
|
|
536
|
+
*
|
|
537
|
+
* This api is the same as used by the native reMarkable extension and works
|
|
538
|
+
* even if the backend schema version is version 4. Setting mime to "folder"
|
|
539
|
+
* allows folder creation.
|
|
540
|
+
*
|
|
541
|
+
* @param visibleName - the name of the file as it should appear on the reMarkable
|
|
542
|
+
* @param bytes - the bytes of the file to upload
|
|
543
|
+
* @param mime - the mime type of the file to upload
|
|
544
|
+
|
|
545
|
+
* @returns a simple entry with the id and hash of the uploaded file
|
|
546
|
+
*/
|
|
547
|
+
uploadFile(visibleName: string, bytes: Uint8Array, mime: UploadMimeType): Promise<SimpleEntry>;
|
|
524
548
|
/**
|
|
525
549
|
* dump the current cache to a string to preserve between session
|
|
526
550
|
*
|
|
@@ -530,15 +554,13 @@ export interface RawRemarkableApi {
|
|
|
530
554
|
/** completely clear the cache */
|
|
531
555
|
clearCache(): void;
|
|
532
556
|
}
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
}): Promise<Response>;
|
|
538
|
-
}
|
|
557
|
+
type AuthedFetch = (method: RequestMethod, url: string, init?: {
|
|
558
|
+
body?: string | Uint8Array;
|
|
559
|
+
headers?: Record<string, string>;
|
|
560
|
+
}) => Promise<Response>;
|
|
539
561
|
export declare class RawRemarkable implements RawRemarkableApi {
|
|
540
562
|
#private;
|
|
541
|
-
constructor(authedFetch: AuthedFetch, cache: Map<string, string | null>, rawHost: string);
|
|
563
|
+
constructor(authedFetch: AuthedFetch, cache: Map<string, string | null>, rawHost: string, uploadHost: string);
|
|
542
564
|
/** make an authorized request to remarkable */
|
|
543
565
|
getRootHash(): Promise<[string, number]>;
|
|
544
566
|
getHash(hash: string): Promise<Uint8Array>;
|
|
@@ -552,6 +574,7 @@ export declare class RawRemarkable implements RawRemarkableApi {
|
|
|
552
574
|
putContent(id: string, content: Content): Promise<[RawFileEntry, Promise<void>]>;
|
|
553
575
|
putMetadata(id: string, metadata: Metadata): Promise<[RawFileEntry, Promise<void>]>;
|
|
554
576
|
putEntries(id: string, entries: RawEntry[]): Promise<[RawListEntry, Promise<void>]>;
|
|
577
|
+
uploadFile(visibleName: string, bytes: Uint8Array, mime: UploadMimeType): Promise<SimpleEntry>;
|
|
555
578
|
dumpCache(): string;
|
|
556
579
|
clearCache(): void;
|
|
557
580
|
}
|
package/dist/raw.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { fromByteArray } from "base64-js";
|
|
2
2
|
import CRC32C from "crc-32/crc32c";
|
|
3
|
-
import { boolean, elements, empty, enumeration, float64, int32, properties, string, timestamp,
|
|
3
|
+
import { boolean, elements, empty, enumeration, float64, int32, nullable, properties, string, timestamp, uint8, uint32, values, } from "jtd-ts";
|
|
4
4
|
import { ValidationError } from "./error.js";
|
|
5
5
|
const hashReg = /^[0-9a-f]{64}$/;
|
|
6
6
|
const tag = properties({
|
|
@@ -74,7 +74,7 @@ const documentContent = properties({
|
|
|
74
74
|
orientation: enumeration("portrait", "landscape"),
|
|
75
75
|
pageCount: uint32(),
|
|
76
76
|
sizeInBytes: string(),
|
|
77
|
-
textAlignment: enumeration("justify", "left"),
|
|
77
|
+
textAlignment: enumeration("", "justify", "left"),
|
|
78
78
|
textScale: float64(),
|
|
79
79
|
}, {
|
|
80
80
|
cPages,
|
|
@@ -90,10 +90,10 @@ const documentContent = properties({
|
|
|
90
90
|
count: uint32(),
|
|
91
91
|
timestamp: float64(),
|
|
92
92
|
}, undefined, true),
|
|
93
|
-
lastOpenedPage:
|
|
93
|
+
lastOpenedPage: int32(),
|
|
94
94
|
margins: uint32(),
|
|
95
95
|
originalPageCount: int32(),
|
|
96
|
-
pages: elements(string()),
|
|
96
|
+
pages: nullable(elements(string())),
|
|
97
97
|
pageTags: elements(pageTag),
|
|
98
98
|
redirectionPageMap: elements(int32()),
|
|
99
99
|
tags: elements(tag),
|
|
@@ -134,7 +134,7 @@ const metadata = properties({
|
|
|
134
134
|
visibleName: string(),
|
|
135
135
|
}, {
|
|
136
136
|
lastOpened: string(),
|
|
137
|
-
lastOpenedPage:
|
|
137
|
+
lastOpenedPage: int32(),
|
|
138
138
|
createdTime: string(),
|
|
139
139
|
deleted: boolean(),
|
|
140
140
|
metadatamodified: boolean(),
|
|
@@ -151,8 +151,14 @@ const rootHash = properties({
|
|
|
151
151
|
generation: float64(),
|
|
152
152
|
schemaVersion: uint8(),
|
|
153
153
|
}, undefined, true);
|
|
154
|
+
const NativeSimpleEntry = properties({
|
|
155
|
+
docID: string(),
|
|
156
|
+
hash: string(),
|
|
157
|
+
}, undefined, true);
|
|
154
158
|
async function digest(buff) {
|
|
155
|
-
const digest = await crypto.subtle.digest("SHA-256",
|
|
159
|
+
const digest = await crypto.subtle.digest("SHA-256",
|
|
160
|
+
// NOTE this is type hinted wrong, but it does work correctly on a uint8 view
|
|
161
|
+
buff);
|
|
156
162
|
return [...new Uint8Array(digest)]
|
|
157
163
|
.map((x) => x.toString(16).padStart(2, "0"))
|
|
158
164
|
.join("");
|
|
@@ -160,6 +166,7 @@ async function digest(buff) {
|
|
|
160
166
|
export class RawRemarkable {
|
|
161
167
|
#authedFetch;
|
|
162
168
|
#rawHost;
|
|
169
|
+
#uploadHost;
|
|
163
170
|
/**
|
|
164
171
|
* a cache of all hashes we know exist
|
|
165
172
|
*
|
|
@@ -170,10 +177,11 @@ export class RawRemarkable {
|
|
|
170
177
|
* not to write a a cached value again, but we'll still need to read it.
|
|
171
178
|
*/
|
|
172
179
|
#cache;
|
|
173
|
-
constructor(authedFetch, cache, rawHost) {
|
|
180
|
+
constructor(authedFetch, cache, rawHost, uploadHost) {
|
|
174
181
|
this.#authedFetch = authedFetch;
|
|
175
182
|
this.#cache = cache;
|
|
176
183
|
this.#rawHost = rawHost;
|
|
184
|
+
this.#uploadHost = uploadHost;
|
|
177
185
|
}
|
|
178
186
|
/** make an authorized request to remarkable */
|
|
179
187
|
async getRootHash() {
|
|
@@ -235,7 +243,7 @@ export class RawRemarkable {
|
|
|
235
243
|
async getEntries(hash) {
|
|
236
244
|
const rawFile = await this.getText(hash);
|
|
237
245
|
const [version, ...rest] = rawFile.slice(0, -1).split("\n");
|
|
238
|
-
if (version
|
|
246
|
+
if (version !== "3") {
|
|
239
247
|
throw new Error(`schema version ${version} not supported`);
|
|
240
248
|
}
|
|
241
249
|
else {
|
|
@@ -253,8 +261,8 @@ export class RawRemarkable {
|
|
|
253
261
|
hash,
|
|
254
262
|
type: 80000000,
|
|
255
263
|
id,
|
|
256
|
-
subfiles: parseInt(subfiles),
|
|
257
|
-
size: parseInt(size),
|
|
264
|
+
subfiles: parseInt(subfiles, 10),
|
|
265
|
+
size: parseInt(size, 10),
|
|
258
266
|
};
|
|
259
267
|
}
|
|
260
268
|
else if (type === "0" && subfiles === "0") {
|
|
@@ -263,7 +271,7 @@ export class RawRemarkable {
|
|
|
263
271
|
type: 0,
|
|
264
272
|
id,
|
|
265
273
|
subfiles: 0,
|
|
266
|
-
size: parseInt(size),
|
|
274
|
+
size: parseInt(size, 10),
|
|
267
275
|
};
|
|
268
276
|
}
|
|
269
277
|
else {
|
|
@@ -418,6 +426,23 @@ export class RawRemarkable {
|
|
|
418
426
|
this.#putFile(hash, `${id}.docSchema`, enc.encode(records.join(""))),
|
|
419
427
|
];
|
|
420
428
|
}
|
|
429
|
+
async uploadFile(visibleName, bytes, mime) {
|
|
430
|
+
const enc = new TextEncoder();
|
|
431
|
+
const meta = fromByteArray(enc.encode(JSON.stringify({ file_name: visibleName })));
|
|
432
|
+
const resp = await this.#authedFetch("POST", `${this.#uploadHost}/doc/v2/files`, {
|
|
433
|
+
body: bytes,
|
|
434
|
+
headers: {
|
|
435
|
+
"Content-Type": mime,
|
|
436
|
+
"rm-meta": meta,
|
|
437
|
+
"rm-source": "RoR-Browser",
|
|
438
|
+
},
|
|
439
|
+
});
|
|
440
|
+
const loaded = (await resp.json());
|
|
441
|
+
if (!NativeSimpleEntry.guardAssert(loaded))
|
|
442
|
+
throw Error("invalid upload response");
|
|
443
|
+
const { docID, hash } = loaded;
|
|
444
|
+
return { id: docID, hash };
|
|
445
|
+
}
|
|
421
446
|
dumpCache() {
|
|
422
447
|
return JSON.stringify(Object.fromEntries(this.#cache));
|
|
423
448
|
}
|