rmapi-js 1.1.0 → 2.1.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 +34 -9
- package/bundle/rmapi-js.cjs.min.js +4 -4
- package/bundle/rmapi-js.esm.min.js +4 -4
- package/bundle/rmapi-js.iife.min.js +4 -4
- package/dist/index.d.ts +166 -32
- package/dist/index.js +224 -69
- package/package.json +33 -29
- package/bundle/rmapi.cjs.min.js +0 -4
- package/bundle/rmapi.esm.min.js +0 -4
- package/bundle/rmapi.iife.min.js +0 -4
package/dist/index.js
CHANGED
|
@@ -1,12 +1,17 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Create and interact with reMarkable cloud.
|
|
3
3
|
*
|
|
4
|
+
* After getting a device token with the {@link register | `register`} method,
|
|
5
|
+
* persist it and create api instances using {@link remarkable | `remarkable`}.
|
|
6
|
+
* Outside of registration, all relevant methods are in
|
|
7
|
+
* {@link RemarkableApi | `RemarkableApi`}.
|
|
8
|
+
*
|
|
4
9
|
* @example
|
|
5
10
|
* A simple fetch
|
|
6
11
|
* ```ts
|
|
7
12
|
* import { register, remarkable } from "rmapi-js";
|
|
8
13
|
*
|
|
9
|
-
* const code = "..." // eight letter code from https://my.remarkable.com/device/
|
|
14
|
+
* const code = "..." // eight letter code from https://my.remarkable.com/device/browser/connect
|
|
10
15
|
* const token = await register(code)
|
|
11
16
|
* // persist token
|
|
12
17
|
* const api = await remarkable(token);
|
|
@@ -35,19 +40,22 @@
|
|
|
35
40
|
* const rootEntries = await api.getEntries(root);
|
|
36
41
|
* rootEntries.push(entry);
|
|
37
42
|
* const { hash } = await api.putEntries("", rootEntries);
|
|
38
|
-
* await api.putRootHash(hash, gen);
|
|
43
|
+
* const nextGen = await api.putRootHash(hash, gen);
|
|
44
|
+
* await api.syncComplete(nextGen);
|
|
39
45
|
* ```
|
|
40
46
|
*
|
|
41
47
|
* @packageDocumentation
|
|
42
48
|
*/
|
|
49
|
+
import { fromByteArray } from "base64-js";
|
|
43
50
|
import { v4 as uuid4 } from "uuid";
|
|
44
51
|
import { concatBuffers, fromHex, toHex } from "./utils";
|
|
45
52
|
import { validate } from "./validate";
|
|
46
53
|
const SCHEMA_VERSION = "3";
|
|
47
|
-
const
|
|
48
|
-
const
|
|
54
|
+
const AUTH_HOST = "https://webapp-prod.cloud.remarkable.engineering";
|
|
55
|
+
const SYNC_HOST = "https://internal.cloud.remarkable.com";
|
|
49
56
|
const GENERATION_HEADER = "x-goog-generation";
|
|
50
57
|
const GENERATION_RACE_HEADER = "x-goog-if-generation-match";
|
|
58
|
+
const CONTENT_LENGTH_RANGE_HEADER = "x-goog-content-length-range";
|
|
51
59
|
/** tool options */
|
|
52
60
|
export const builtinTools = [
|
|
53
61
|
"Ballpoint",
|
|
@@ -117,6 +125,12 @@ export const builtinLineHeights = {
|
|
|
117
125
|
/** double */
|
|
118
126
|
xl: 200,
|
|
119
127
|
};
|
|
128
|
+
const uploadEntrySchema = {
|
|
129
|
+
properties: {
|
|
130
|
+
docID: { type: "string" },
|
|
131
|
+
hash: { type: "string" },
|
|
132
|
+
},
|
|
133
|
+
};
|
|
120
134
|
const urlResponseSchema = {
|
|
121
135
|
properties: {
|
|
122
136
|
relative_path: { type: "string" },
|
|
@@ -124,19 +138,22 @@ const urlResponseSchema = {
|
|
|
124
138
|
expires: { type: "timestamp" },
|
|
125
139
|
method: { enum: ["POST", "GET", "PUT", "DELETE"] },
|
|
126
140
|
},
|
|
141
|
+
optionalProperties: {
|
|
142
|
+
maxuploadsize_bytes: { type: "float64" },
|
|
143
|
+
},
|
|
127
144
|
};
|
|
128
145
|
const commonProperties = {
|
|
129
146
|
visibleName: { type: "string" },
|
|
130
|
-
parent: { type: "string" },
|
|
131
147
|
lastModified: { type: "string" },
|
|
132
|
-
version: { type: "int32" },
|
|
133
|
-
synced: { type: "boolean" },
|
|
134
148
|
};
|
|
135
149
|
const commonOptionalProperties = {
|
|
150
|
+
version: { type: "int32" },
|
|
136
151
|
pinned: { type: "boolean" },
|
|
152
|
+
synced: { type: "boolean" },
|
|
137
153
|
modified: { type: "boolean" },
|
|
138
154
|
deleted: { type: "boolean" },
|
|
139
155
|
metadatamodified: { type: "boolean" },
|
|
156
|
+
parent: { type: "string" },
|
|
140
157
|
};
|
|
141
158
|
const metadataSchema = {
|
|
142
159
|
discriminator: "type",
|
|
@@ -155,6 +172,34 @@ const metadataSchema = {
|
|
|
155
172
|
},
|
|
156
173
|
},
|
|
157
174
|
};
|
|
175
|
+
const baseMetadataProperties = {
|
|
176
|
+
id: { type: "string" },
|
|
177
|
+
hash: { type: "string" },
|
|
178
|
+
};
|
|
179
|
+
const metadataEntrySchema = {
|
|
180
|
+
discriminator: "type",
|
|
181
|
+
mapping: {
|
|
182
|
+
CollectionType: {
|
|
183
|
+
properties: {
|
|
184
|
+
...commonProperties,
|
|
185
|
+
...baseMetadataProperties,
|
|
186
|
+
},
|
|
187
|
+
optionalProperties: commonOptionalProperties,
|
|
188
|
+
},
|
|
189
|
+
DocumentType: {
|
|
190
|
+
properties: {
|
|
191
|
+
...commonProperties,
|
|
192
|
+
...baseMetadataProperties,
|
|
193
|
+
fileType: { enum: ["notebook", "epub", "pdf", ""] },
|
|
194
|
+
},
|
|
195
|
+
optionalProperties: {
|
|
196
|
+
...commonOptionalProperties,
|
|
197
|
+
lastOpened: { type: "string" },
|
|
198
|
+
lastOpenedPage: { type: "int32" },
|
|
199
|
+
},
|
|
200
|
+
},
|
|
201
|
+
},
|
|
202
|
+
};
|
|
158
203
|
/** an error that results from a failed request */
|
|
159
204
|
export class ResponseError extends Error {
|
|
160
205
|
/** the response status number */
|
|
@@ -181,18 +226,18 @@ export class GenerationError extends Error {
|
|
|
181
226
|
/**
|
|
182
227
|
* register a device and get the token needed to access the api
|
|
183
228
|
*
|
|
184
|
-
* Have users go to `https://my.remarkable.com/device/
|
|
229
|
+
* Have users go to `https://my.remarkable.com/device/browser/connect` and pass
|
|
185
230
|
* the resulting code into this function to get a device token. Persist that
|
|
186
231
|
* token to use the api.
|
|
187
232
|
*
|
|
188
|
-
* @param code - the eight letter code a user got from `https://my.remarkable.com/device/
|
|
233
|
+
* @param code - the eight letter code a user got from `https://my.remarkable.com/device/browser/connect`.
|
|
189
234
|
* @returns the device token necessary for creating an api instace. These never expire so persist as long as necessary.
|
|
190
235
|
*/
|
|
191
|
-
export async function register(code, { deviceDesc = "
|
|
236
|
+
export async function register(code, { deviceDesc = "browser-chrome", uuid = uuid4(), authHost = AUTH_HOST, fetch = globalThis.fetch, } = {}) {
|
|
192
237
|
if (code.length !== 8) {
|
|
193
238
|
throw new Error(`code should be length 8, but was ${code.length}`);
|
|
194
239
|
}
|
|
195
|
-
const resp = await fetch(`${
|
|
240
|
+
const resp = await fetch(`${authHost}/token/json/2/device/new`, {
|
|
196
241
|
method: "POST",
|
|
197
242
|
headers: {
|
|
198
243
|
Authorization: "Bearer",
|
|
@@ -253,37 +298,45 @@ export function parseEntry(line) {
|
|
|
253
298
|
}
|
|
254
299
|
/** the implementation of that api */
|
|
255
300
|
class Remarkable {
|
|
256
|
-
userToken;
|
|
257
|
-
fetch;
|
|
258
|
-
cache;
|
|
259
|
-
subtle;
|
|
260
|
-
|
|
261
|
-
constructor(userToken, fetch, cache, subtle,
|
|
262
|
-
this
|
|
263
|
-
this
|
|
264
|
-
this
|
|
265
|
-
this
|
|
266
|
-
this
|
|
301
|
+
#userToken;
|
|
302
|
+
#fetch;
|
|
303
|
+
#cache;
|
|
304
|
+
#subtle;
|
|
305
|
+
#syncHost;
|
|
306
|
+
constructor(userToken, fetch, cache, subtle, syncHost) {
|
|
307
|
+
this.#userToken = userToken;
|
|
308
|
+
this.#fetch = fetch;
|
|
309
|
+
this.#cache = cache;
|
|
310
|
+
this.#subtle = subtle;
|
|
311
|
+
this.#syncHost = syncHost;
|
|
267
312
|
}
|
|
268
313
|
/** make an authorized request to remarkable */
|
|
269
|
-
async authedFetch(url, body, method = "POST") {
|
|
270
|
-
const resp = await this
|
|
314
|
+
async #authedFetch(url, { body, method = "POST", headers = {}, }) {
|
|
315
|
+
const resp = await this.#fetch(url, {
|
|
271
316
|
method,
|
|
272
317
|
headers: {
|
|
273
|
-
Authorization: `Bearer ${this
|
|
318
|
+
Authorization: `Bearer ${this.#userToken}`,
|
|
319
|
+
...headers,
|
|
274
320
|
},
|
|
275
|
-
body
|
|
321
|
+
body,
|
|
276
322
|
});
|
|
277
323
|
if (!resp.ok) {
|
|
278
|
-
|
|
324
|
+
const msg = await resp.text();
|
|
325
|
+
throw new ResponseError(resp.status, resp.statusText, `failed reMarkable request: ${msg}`);
|
|
279
326
|
}
|
|
280
327
|
else {
|
|
281
328
|
return resp;
|
|
282
329
|
}
|
|
283
330
|
}
|
|
284
331
|
/** make a signed request to the cloud */
|
|
285
|
-
async signedFetch({ url, method }, body,
|
|
286
|
-
const
|
|
332
|
+
async #signedFetch({ url, method, maxuploadsize_bytes }, body, add_headers = {}) {
|
|
333
|
+
const headers = maxuploadsize_bytes
|
|
334
|
+
? {
|
|
335
|
+
...add_headers,
|
|
336
|
+
[CONTENT_LENGTH_RANGE_HEADER]: `0,${maxuploadsize_bytes}`,
|
|
337
|
+
}
|
|
338
|
+
: add_headers;
|
|
339
|
+
const resp = await this.#fetch(url, {
|
|
287
340
|
method,
|
|
288
341
|
body,
|
|
289
342
|
headers,
|
|
@@ -297,14 +350,13 @@ class Remarkable {
|
|
|
297
350
|
}
|
|
298
351
|
}
|
|
299
352
|
/** get the details for how to make a signed request to remarkable cloud */
|
|
300
|
-
async getUrl(relativePath, gen) {
|
|
353
|
+
async #getUrl(relativePath, gen, rootHash) {
|
|
301
354
|
const key = gen === undefined ? "downloads" : "uploads";
|
|
302
|
-
|
|
303
|
-
const
|
|
304
|
-
http_method:
|
|
305
|
-
relative_path: relativePath
|
|
306
|
-
|
|
307
|
-
});
|
|
355
|
+
// NOTE this is done manually to serialize the bigints appropriately
|
|
356
|
+
const body = rootHash
|
|
357
|
+
? `{ "http_method": "PUT", "relative_path": "${relativePath}", "root_schema": "${rootHash}", "generation": ${gen} }`
|
|
358
|
+
: JSON.stringify({ http_method: "GET", relative_path: relativePath });
|
|
359
|
+
const resp = await this.#authedFetch(`${this.#syncHost}/sync/v2/signed-urls/${key}`, { body });
|
|
308
360
|
const raw = await resp.text();
|
|
309
361
|
const res = JSON.parse(raw);
|
|
310
362
|
validate(urlResponseSchema, res);
|
|
@@ -314,8 +366,8 @@ class Remarkable {
|
|
|
314
366
|
* get the root hash and the current generation
|
|
315
367
|
*/
|
|
316
368
|
async getRootHash() {
|
|
317
|
-
const signed = await this
|
|
318
|
-
const resp = await this
|
|
369
|
+
const signed = await this.#getUrl("root");
|
|
370
|
+
const resp = await this.#signedFetch(signed);
|
|
319
371
|
const generation = resp.headers.get(GENERATION_HEADER);
|
|
320
372
|
if (!generation) {
|
|
321
373
|
throw new Error("no generation header in root hash");
|
|
@@ -326,10 +378,10 @@ class Remarkable {
|
|
|
326
378
|
* write the root hash, incrementing from the current generation
|
|
327
379
|
*/
|
|
328
380
|
async putRootHash(hash, generation) {
|
|
329
|
-
const signed = await this
|
|
381
|
+
const signed = await this.#getUrl("root", generation, hash);
|
|
330
382
|
let resp;
|
|
331
383
|
try {
|
|
332
|
-
resp = await this
|
|
384
|
+
resp = await this.#signedFetch(signed, hash, {
|
|
333
385
|
[GENERATION_RACE_HEADER]: `${generation}`,
|
|
334
386
|
});
|
|
335
387
|
}
|
|
@@ -351,23 +403,23 @@ class Remarkable {
|
|
|
351
403
|
* get text content associated with hash
|
|
352
404
|
*/
|
|
353
405
|
async getBuffer(hash) {
|
|
354
|
-
const signed = await this
|
|
355
|
-
const resp = await this
|
|
406
|
+
const signed = await this.#getUrl(hash);
|
|
407
|
+
const resp = await this.#signedFetch(signed);
|
|
356
408
|
return await resp.arrayBuffer();
|
|
357
409
|
}
|
|
358
410
|
/**
|
|
359
411
|
* get text content associated with hash
|
|
360
412
|
*/
|
|
361
413
|
async getText(hash) {
|
|
362
|
-
const cached =
|
|
414
|
+
const cached = await this.#cache?.get(hash);
|
|
363
415
|
if (cached) {
|
|
364
416
|
return cached;
|
|
365
417
|
}
|
|
366
418
|
else {
|
|
367
|
-
const signed = await this
|
|
368
|
-
const resp = await this
|
|
419
|
+
const signed = await this.#getUrl(hash);
|
|
420
|
+
const resp = await this.#signedFetch(signed);
|
|
369
421
|
const raw = await resp.text();
|
|
370
|
-
|
|
422
|
+
await this.#cache?.set(hash, raw);
|
|
371
423
|
return raw;
|
|
372
424
|
}
|
|
373
425
|
}
|
|
@@ -393,9 +445,9 @@ class Remarkable {
|
|
|
393
445
|
return lines.map(parseEntry);
|
|
394
446
|
}
|
|
395
447
|
/** upload data to hash */
|
|
396
|
-
async putHash(hash, body) {
|
|
397
|
-
const signed = await this
|
|
398
|
-
await this
|
|
448
|
+
async #putHash(hash, body) {
|
|
449
|
+
const signed = await this.#getUrl(hash, null);
|
|
450
|
+
await this.#signedFetch(signed, body);
|
|
399
451
|
}
|
|
400
452
|
/** put a reference to a set of entries into the cloud */
|
|
401
453
|
async putEntries(documentId, entries) {
|
|
@@ -403,13 +455,13 @@ class Remarkable {
|
|
|
403
455
|
const enc = new TextEncoder();
|
|
404
456
|
entries.sort((a, b) => a.documentId.localeCompare(b.documentId));
|
|
405
457
|
const hashes = concatBuffers(entries.map((ent) => fromHex(ent.hash)));
|
|
406
|
-
const digest = await this
|
|
458
|
+
const digest = await this.#subtle.digest("SHA-256", hashes);
|
|
407
459
|
const hash = toHex(digest);
|
|
408
460
|
const entryContents = entries.map(formatEntry).join("");
|
|
409
461
|
const contents = `${SCHEMA_VERSION}\n${entryContents}`;
|
|
410
462
|
const buffer = enc.encode(contents);
|
|
411
|
-
await this
|
|
412
|
-
|
|
463
|
+
await this.#putHash(hash, buffer);
|
|
464
|
+
await this.#cache?.set(hash, contents);
|
|
413
465
|
return {
|
|
414
466
|
hash,
|
|
415
467
|
type: "80000000",
|
|
@@ -420,9 +472,9 @@ class Remarkable {
|
|
|
420
472
|
}
|
|
421
473
|
/** put a raw buffer in the cloud */
|
|
422
474
|
async putBuffer(documentId, buffer) {
|
|
423
|
-
const digest = await this
|
|
475
|
+
const digest = await this.#subtle.digest("SHA-256", buffer);
|
|
424
476
|
const hash = toHex(digest);
|
|
425
|
-
await this
|
|
477
|
+
await this.#putHash(hash, buffer);
|
|
426
478
|
return {
|
|
427
479
|
hash,
|
|
428
480
|
type: "0",
|
|
@@ -435,16 +487,43 @@ class Remarkable {
|
|
|
435
487
|
async putText(documentId, contents) {
|
|
436
488
|
const enc = new TextEncoder();
|
|
437
489
|
const entry = await this.putBuffer(documentId, enc.encode(contents));
|
|
438
|
-
|
|
490
|
+
await this.#cache?.set(entry.hash, contents);
|
|
439
491
|
return entry;
|
|
440
492
|
}
|
|
441
|
-
/**
|
|
442
|
-
async
|
|
493
|
+
/** put metadata into the cloud */
|
|
494
|
+
async putMetadata(documentId, metadata) {
|
|
495
|
+
return await this.putText(`${documentId}.metadata`, JSON.stringify(metadata));
|
|
496
|
+
}
|
|
497
|
+
/** put a new collection (folder) */
|
|
498
|
+
async putCollection(visibleName, parent = "") {
|
|
499
|
+
const documentId = uuid4();
|
|
500
|
+
const lastModified = `${new Date().valueOf()}`;
|
|
501
|
+
const entryPromises = [];
|
|
502
|
+
// upload metadata
|
|
503
|
+
const metadata = {
|
|
504
|
+
type: "CollectionType",
|
|
505
|
+
visibleName,
|
|
506
|
+
version: 0,
|
|
507
|
+
parent,
|
|
508
|
+
synced: true,
|
|
509
|
+
lastModified,
|
|
510
|
+
};
|
|
511
|
+
entryPromises.push(this.putMetadata(documentId, metadata));
|
|
512
|
+
entryPromises.push(this.putText(`${documentId}.content`, "{}"));
|
|
513
|
+
const entries = await Promise.all(entryPromises);
|
|
514
|
+
return await this.putEntries(documentId, entries);
|
|
515
|
+
}
|
|
516
|
+
/** upload a content file */
|
|
517
|
+
async #putContent(visibleName, buffer, fileType, parent, content) {
|
|
518
|
+
/* istanbul ignore if */
|
|
519
|
+
if (content.fileType !== fileType) {
|
|
520
|
+
throw new Error(`internal error: fileTypes don't match: ${fileType}, ${content.fileType}`);
|
|
521
|
+
}
|
|
443
522
|
const documentId = uuid4();
|
|
444
523
|
const lastModified = `${new Date().valueOf()}`;
|
|
445
524
|
const entryPromises = [];
|
|
446
525
|
// upload main document
|
|
447
|
-
entryPromises.push(this.putBuffer(`${documentId}
|
|
526
|
+
entryPromises.push(this.putBuffer(`${documentId}.${fileType}`, buffer));
|
|
448
527
|
// upload metadata
|
|
449
528
|
const metadata = {
|
|
450
529
|
type: "DocumentType",
|
|
@@ -454,7 +533,16 @@ class Remarkable {
|
|
|
454
533
|
synced: true,
|
|
455
534
|
lastModified,
|
|
456
535
|
};
|
|
457
|
-
entryPromises.push(this.
|
|
536
|
+
entryPromises.push(this.putMetadata(documentId, metadata));
|
|
537
|
+
entryPromises.push(this.putText(`${documentId}.content`, JSON.stringify(content)));
|
|
538
|
+
// NOTE we technically get the entries a bit earlier, so could upload this
|
|
539
|
+
// before all contents are uploaded, but this also saves us from uploading
|
|
540
|
+
// the contents entry before all have uploaded successfully
|
|
541
|
+
const entries = await Promise.all(entryPromises);
|
|
542
|
+
return await this.putEntries(documentId, entries);
|
|
543
|
+
}
|
|
544
|
+
/** upload an epub */
|
|
545
|
+
async putEpub(visibleName, buffer, { parent = "", margins = 125, orientation, textAlignment, textScale = 1, lineHeight = -1, fontName = "", cover = "visited", lastTool, } = {}) {
|
|
458
546
|
// upload content file
|
|
459
547
|
const content = {
|
|
460
548
|
dummyDocument: false,
|
|
@@ -478,12 +566,78 @@ class Remarkable {
|
|
|
478
566
|
textAlignment,
|
|
479
567
|
fontName,
|
|
480
568
|
};
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
569
|
+
return await this.#putContent(visibleName, buffer, "epub", parent, content);
|
|
570
|
+
}
|
|
571
|
+
/** upload a pdf */
|
|
572
|
+
async putPdf(visibleName, buffer, { parent = "", orientation, cover = "first", lastTool } = {}) {
|
|
573
|
+
// upload content file
|
|
574
|
+
const content = {
|
|
575
|
+
dummyDocument: false,
|
|
576
|
+
extraMetadata: {
|
|
577
|
+
LastTool: lastTool,
|
|
578
|
+
},
|
|
579
|
+
fileType: "pdf",
|
|
580
|
+
pageCount: 0,
|
|
581
|
+
lastOpenedPage: 0,
|
|
582
|
+
lineHeight: -1,
|
|
583
|
+
margins: 125,
|
|
584
|
+
textScale: 1,
|
|
585
|
+
pages: [],
|
|
586
|
+
coverPageNumber: cover === "first" ? 0 : -1,
|
|
587
|
+
formatVersion: 1,
|
|
588
|
+
orientation,
|
|
589
|
+
};
|
|
590
|
+
return await this.#putContent(visibleName, buffer, "pdf", parent, content);
|
|
591
|
+
}
|
|
592
|
+
/** send sync complete request */
|
|
593
|
+
async syncComplete(generation) {
|
|
594
|
+
// NOTE this is done manually to properly serialize the bigint
|
|
595
|
+
const body = `{ "generation": ${generation} }`;
|
|
596
|
+
await this.#authedFetch(`${this.#syncHost}/sync/v2/sync-complete`, {
|
|
597
|
+
body,
|
|
598
|
+
});
|
|
599
|
+
}
|
|
600
|
+
/** get entries and metadata for all files */
|
|
601
|
+
async getEntriesMetadata() {
|
|
602
|
+
const resp = await this.#authedFetch(`${this.#syncHost}/doc/v2/files`, {
|
|
603
|
+
method: "GET",
|
|
604
|
+
headers: {
|
|
605
|
+
"rm-source": "RoR-Browser",
|
|
606
|
+
},
|
|
607
|
+
});
|
|
608
|
+
const raw = await resp.text();
|
|
609
|
+
const res = JSON.parse(raw);
|
|
610
|
+
const schema = {
|
|
611
|
+
elements: metadataEntrySchema,
|
|
612
|
+
};
|
|
613
|
+
validate(schema, res);
|
|
614
|
+
return res;
|
|
615
|
+
}
|
|
616
|
+
/** upload a file */
|
|
617
|
+
async #uploadFile(visibleName, buffer, contentType) {
|
|
618
|
+
const encoder = new TextEncoder();
|
|
619
|
+
const meta = encoder.encode(JSON.stringify({ file_name: visibleName }));
|
|
620
|
+
const resp = await this.#authedFetch(`${this.#syncHost}/doc/v2/files`, {
|
|
621
|
+
body: buffer,
|
|
622
|
+
headers: {
|
|
623
|
+
"content-type": contentType,
|
|
624
|
+
"rm-meta": fromByteArray(meta),
|
|
625
|
+
"rm-source": "RoR-Browser",
|
|
626
|
+
},
|
|
627
|
+
});
|
|
628
|
+
const raw = await resp.text();
|
|
629
|
+
const res = JSON.parse(raw);
|
|
630
|
+
validate(uploadEntrySchema, res);
|
|
631
|
+
return res;
|
|
632
|
+
}
|
|
633
|
+
/** upload an epub */
|
|
634
|
+
async uploadEpub(visibleName, buffer) {
|
|
635
|
+
return await this.#uploadFile(visibleName, buffer, "application/epub+zip");
|
|
636
|
+
}
|
|
637
|
+
/** upload a pdf */
|
|
638
|
+
async uploadPdf(visibleName, buffer) {
|
|
639
|
+
// TODO why doesn't this work
|
|
640
|
+
return await this.#uploadFile(visibleName, buffer, "application/pdf");
|
|
487
641
|
}
|
|
488
642
|
}
|
|
489
643
|
/**
|
|
@@ -492,11 +646,12 @@ class Remarkable {
|
|
|
492
646
|
* This gets a temporary authentication token with the device token. If
|
|
493
647
|
* requests start failing, simply recreate the api instance.
|
|
494
648
|
*
|
|
495
|
-
* @param deviceToken - the device token proving this api instance is
|
|
649
|
+
* @param deviceToken - the device token proving this api instance is
|
|
650
|
+
* registered. Create one with {@link register}.
|
|
496
651
|
* @returns an api instance
|
|
497
652
|
*/
|
|
498
|
-
export async function remarkable(deviceToken, { fetch = globalThis.fetch, cache, subtle = globalThis.crypto?.subtle,
|
|
499
|
-
const resp = await fetch(`${
|
|
653
|
+
export async function remarkable(deviceToken, { fetch = globalThis.fetch, cache, subtle = globalThis.crypto?.subtle, authHost = AUTH_HOST, syncHost = SYNC_HOST, } = {}) {
|
|
654
|
+
const resp = await fetch(`${authHost}/token/json/2/user/new`, {
|
|
500
655
|
method: "POST",
|
|
501
656
|
headers: {
|
|
502
657
|
Authorization: `Bearer ${deviceToken}`,
|
|
@@ -506,5 +661,5 @@ export async function remarkable(deviceToken, { fetch = globalThis.fetch, cache,
|
|
|
506
661
|
throw new Error(`couldn't fetch auth token: ${resp.statusText}`);
|
|
507
662
|
}
|
|
508
663
|
const userToken = await resp.text();
|
|
509
|
-
return new Remarkable(userToken, fetch, cache, subtle,
|
|
664
|
+
return new Remarkable(userToken, fetch, cache, subtle, syncHost);
|
|
510
665
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "rmapi-js",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.1.0",
|
|
4
4
|
"description": "JavaScript implementation of the reMarkable 1.5 api",
|
|
5
5
|
"repository": "git@github.com:erikbrinkman/rmapi-js.git",
|
|
6
6
|
"author": "Erik Brinkman <erik.brinkman@gmail.com>",
|
|
@@ -8,54 +8,55 @@
|
|
|
8
8
|
"keywords": [
|
|
9
9
|
"remarkable"
|
|
10
10
|
],
|
|
11
|
-
"types": "dist/index.d.ts",
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
12
|
"module": "bundle/rmapi-js.esm.min.js",
|
|
13
13
|
"main": "bundle/rmapi-js.cjs.min.js",
|
|
14
14
|
"unpkg": "bundle/rmapi-js.iife.min.js",
|
|
15
15
|
"files": [
|
|
16
|
-
"
|
|
17
|
-
"
|
|
18
|
-
"
|
|
16
|
+
"./bundle/*.js",
|
|
17
|
+
"./dist/**/*.js",
|
|
18
|
+
"./dist/**/*.d.ts"
|
|
19
19
|
],
|
|
20
|
-
"packageManager": "yarn@3.
|
|
20
|
+
"packageManager": "yarn@3.3.1",
|
|
21
21
|
"scripts": {
|
|
22
22
|
"doc": "typedoc",
|
|
23
|
-
"fmt": "prettier --cache --write 'src/*.ts' '*.json'
|
|
24
|
-
"lint": "tsc && eslint --cache 'src/*.ts'
|
|
23
|
+
"fmt": "prettier --cache --write 'src/*.ts' '*.json' bundle.mjs",
|
|
24
|
+
"lint": "tsc && eslint --cache 'src/*.ts' bundle.mjs && typedoc --emit none",
|
|
25
25
|
"test": "jest --coverage",
|
|
26
26
|
"build": "tsc -p tsconfig.build.json && yarn node bundle.mjs",
|
|
27
27
|
"prepack": "yarn lint && yarn test && yarn build"
|
|
28
28
|
},
|
|
29
29
|
"dependencies": {
|
|
30
|
-
"ajv": "^8.11.
|
|
30
|
+
"ajv": "^8.11.2",
|
|
31
|
+
"base64-js": "^1.5.1",
|
|
32
|
+
"jtd": "^0.1.1",
|
|
31
33
|
"uuid": "^9.0.0"
|
|
32
34
|
},
|
|
33
35
|
"devDependencies": {
|
|
34
|
-
"@babel/core": "^7.
|
|
35
|
-
"@babel/preset-env": "^7.
|
|
36
|
+
"@babel/core": "^7.20.7",
|
|
37
|
+
"@babel/preset-env": "^7.20.2",
|
|
36
38
|
"@babel/preset-typescript": "^7.18.6",
|
|
37
|
-
"@types/babel__core": "^7.1.
|
|
39
|
+
"@types/babel__core": "^7.1.20",
|
|
38
40
|
"@types/babel__preset-env": "^7.9.2",
|
|
39
|
-
"@types/jest": "^29.
|
|
40
|
-
"@types/node": "^18.
|
|
41
|
-
"@types/uuid": "^
|
|
42
|
-
"@typescript-eslint/eslint-plugin": "^5.
|
|
43
|
-
"@typescript-eslint/parser": "^5.
|
|
41
|
+
"@types/jest": "^29.2.4",
|
|
42
|
+
"@types/node": "^18.11.18",
|
|
43
|
+
"@types/uuid": "^9.0.0",
|
|
44
|
+
"@typescript-eslint/eslint-plugin": "^5.47.1",
|
|
45
|
+
"@typescript-eslint/parser": "^5.47.1",
|
|
44
46
|
"@yarnpkg/esbuild-plugin-pnp": "^3.0.0-rc.15",
|
|
45
|
-
"babel-jest": "^29.1
|
|
46
|
-
"chalk": "^5.
|
|
47
|
-
"esbuild": "^0.
|
|
48
|
-
"eslint": "^8.
|
|
47
|
+
"babel-jest": "^29.3.1",
|
|
48
|
+
"chalk": "^5.2.0",
|
|
49
|
+
"esbuild": "^0.16.10",
|
|
50
|
+
"eslint": "^8.30.0",
|
|
49
51
|
"eslint-config-prettier": "^8.5.0",
|
|
50
|
-
"eslint-plugin-jest": "^27.1.
|
|
51
|
-
"eslint-plugin-spellcheck": "^0.0.
|
|
52
|
+
"eslint-plugin-jest": "^27.1.7",
|
|
53
|
+
"eslint-plugin-spellcheck": "^0.0.20",
|
|
52
54
|
"eslint-plugin-tsdoc": "^0.2.17",
|
|
53
|
-
"jest": "^29.1
|
|
54
|
-
"
|
|
55
|
-
"prettier": "^2.
|
|
56
|
-
"
|
|
57
|
-
"
|
|
58
|
-
"typescript": "^4.8.4"
|
|
55
|
+
"jest": "^29.3.1",
|
|
56
|
+
"prettier": "^2.8.1",
|
|
57
|
+
"prettier-plugin-organize-imports": "^3.2.1",
|
|
58
|
+
"typedoc": "^0.23.23",
|
|
59
|
+
"typescript": "^4.9.4"
|
|
59
60
|
},
|
|
60
61
|
"prettier": {
|
|
61
62
|
"plugins": [
|
|
@@ -94,6 +95,7 @@
|
|
|
94
95
|
"node": true
|
|
95
96
|
},
|
|
96
97
|
"rules": {
|
|
98
|
+
"@typescript-eslint/no-inferrable-types": "off",
|
|
97
99
|
"tsdoc/syntax": "error",
|
|
98
100
|
"no-warning-comments": [
|
|
99
101
|
"error",
|
|
@@ -121,6 +123,7 @@
|
|
|
121
123
|
"Paintbrushv",
|
|
122
124
|
"Pencilv",
|
|
123
125
|
"authed",
|
|
126
|
+
"bigints",
|
|
124
127
|
"ebooks",
|
|
125
128
|
"epub",
|
|
126
129
|
"fineliner",
|
|
@@ -128,6 +131,7 @@
|
|
|
128
131
|
"iife",
|
|
129
132
|
"incrementing",
|
|
130
133
|
"linux",
|
|
134
|
+
"macos",
|
|
131
135
|
"rmapi",
|
|
132
136
|
"subfiles",
|
|
133
137
|
"urls",
|