rmapi-js 8.4.0 → 9.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 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 `listFiles` to explore entries of different file
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.listFiles();
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 listFiles)
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
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, CPages, CPageStringValue, CPageUUID, DocumentContent, DocumentMetadata, FileType, KeyboardMetadata, Metadata, Orientation, PageTag, RawEntry, RawFileEntry, RawListEntry, RawRemarkableApi, SimpleEntry, Tag, TemplateContent, TextAlignment, UploadMimeType, ZoomMode, } from "./raw";
3
+ export type { BackgroundFilter, CollectionContent, Content, CPageNumberValue, CPagePage, CPageStringValue, CPages, CPageUUID, DocumentContent, DocumentMetadata, Entries, FileType, KeyboardMetadata, Metadata, Orientation, PageTag, RawEntry, RawRemarkableApi, SchemaVersion, 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 */
@@ -226,7 +226,7 @@ export interface RemarkableApi {
226
226
  * @remarks
227
227
  * If this fails validation and you still want to get the content, you can use
228
228
  * the low-level api to get the raw text of the `.content` file in the
229
- * `RawListEntry` for this hash.
229
+ * `RawEntry` for this hash.
230
230
  *
231
231
  * @param hash - the hash of the item to get content for
232
232
  * @returns the content
@@ -241,7 +241,7 @@ export interface RemarkableApi {
241
241
  * @remarks
242
242
  * If this fails validation and you still want to get the content, you can use
243
243
  * the low-level api to get the raw text of the `.metadata` file in the
244
- * `RawListEntry` for this hash.
244
+ * `RawEntry` for this hash.
245
245
  *
246
246
  * @param hash - the hash of the item to get metadata for
247
247
  * @returns the metadata
@@ -482,14 +482,17 @@ export interface RemarkableApi {
482
482
  */
483
483
  clearCache(): void;
484
484
  }
485
- /** options for a remarkable instance */
486
- export interface RemarkableOptions {
485
+ /** configuration for exchanging a device token */
486
+ export interface AuthOptions {
487
487
  /**
488
488
  * the url for making authorization requests
489
489
  *
490
490
  * @defaultValue "https://webapp-prod.cloud.remarkable.engineering"
491
491
  */
492
492
  authHost?: string;
493
+ }
494
+ /** options for constructing an api instance from a session token */
495
+ export interface RemarkableSessionOptions {
493
496
  /**
494
497
  * the url for making synchronization requests
495
498
  *
@@ -526,14 +529,35 @@ export interface RemarkableOptions {
526
529
  */
527
530
  maxCacheSize?: number;
528
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;
529
553
  /**
530
554
  * create an instance of the api
531
555
  *
532
- * This gets a temporary authentication token with the device token. If
533
- * requests start failing, simply recreate the api instance.
556
+ * This gets a temporary authentication token with the device token and then
557
+ * constructs the api instance.
534
558
  *
535
559
  * @param deviceToken - the device token proving this api instance is
536
560
  * registered. Create one with {@link register}.
537
561
  * @returns an api instance
538
562
  */
539
- export declare function remarkable(deviceToken: string, { authHost, rawHost, uploadHost, cache, maxCacheSize, }?: RemarkableOptions): Promise<RemarkableApi>;
563
+ export declare function remarkable(deviceToken: string, options?: RemarkableOptions): Promise<RemarkableApi>;
package/dist/index.js CHANGED
@@ -126,13 +126,13 @@ export async function register(code, { deviceDesc = "browser-chrome", uuid = uui
126
126
  }
127
127
  /** the implementation of that api */
128
128
  class Remarkable {
129
- #userToken;
129
+ #sessionToken;
130
130
  /** the same cache that underlies the raw api, allowing us to modify it */
131
131
  #cache;
132
132
  raw;
133
133
  #lastHashGen;
134
- constructor(userToken, rawHost, uploadHost, cache) {
135
- this.#userToken = userToken;
134
+ constructor(sessionToken, rawHost, uploadHost, cache) {
135
+ this.#sessionToken = sessionToken;
136
136
  this.#cache = cache;
137
137
  this.raw = new RawRemarkable((method, url, { body, headers } = {}) => this.#authedFetch(url, { method, body, headers }), cache, rawHost, uploadHost);
138
138
  }
@@ -144,7 +144,9 @@ class Remarkable {
144
144
  }
145
145
  async #putRootHash(hash, generation) {
146
146
  try {
147
- this.#lastHashGen = await this.raw.putRootHash(hash, generation);
147
+ const [rootHash, gen] = await this.raw.putRootHash(hash, generation);
148
+ const [, , schemaVersion] = this.#lastHashGen; // guaranteed to be set
149
+ this.#lastHashGen = [rootHash, gen, schemaVersion];
148
150
  }
149
151
  catch (ex) {
150
152
  // if we hit a generation error, invalidate our cached generation
@@ -158,7 +160,7 @@ class Remarkable {
158
160
  const resp = await fetch(url, {
159
161
  method,
160
162
  headers: {
161
- Authorization: `Bearer ${this.#userToken}`,
163
+ Authorization: `Bearer ${this.#sessionToken}`,
162
164
  ...headers,
163
165
  },
164
166
  // fetch works correctly with uint8 arrays, but is not hinted correctly
@@ -178,7 +180,7 @@ class Remarkable {
178
180
  }
179
181
  }
180
182
  async #convertEntry({ hash, id }) {
181
- const entries = await this.raw.getEntries(hash);
183
+ const { entries } = await this.raw.getEntries(hash);
182
184
  const metaEnt = entries.find((ent) => ent.id.endsWith(".metadata"));
183
185
  const contentEnt = entries.find((ent) => ent.id.endsWith(".content"));
184
186
  if (metaEnt === undefined) {
@@ -238,11 +240,11 @@ class Remarkable {
238
240
  }
239
241
  async listIds(refresh = false) {
240
242
  const [hash] = await this.#getRootHash(refresh);
241
- const entries = await this.raw.getEntries(hash);
243
+ const { entries } = await this.raw.getEntries(hash);
242
244
  return entries.map(({ id, hash }) => ({ id, hash }));
243
245
  }
244
246
  async getContent(hash) {
245
- const entries = await this.raw.getEntries(hash);
247
+ const { entries } = await this.raw.getEntries(hash);
246
248
  const [cont] = entries.filter((e) => e.id.endsWith(".content"));
247
249
  if (cont === undefined) {
248
250
  throw new Error(`couldn't find contents for hash ${hash}`);
@@ -252,7 +254,7 @@ class Remarkable {
252
254
  }
253
255
  }
254
256
  async getMetadata(hash) {
255
- const entries = await this.raw.getEntries(hash);
257
+ const { entries } = await this.raw.getEntries(hash);
256
258
  const [meta] = entries.filter((e) => e.id.endsWith(".metadata"));
257
259
  if (meta === undefined) {
258
260
  throw new Error(`couldn't find metadata for hash ${hash}`);
@@ -262,7 +264,7 @@ class Remarkable {
262
264
  }
263
265
  }
264
266
  async getPdf(hash) {
265
- const entries = await this.raw.getEntries(hash);
267
+ const { entries } = await this.raw.getEntries(hash);
266
268
  const [pdf] = entries.filter((e) => e.id.endsWith(".pdf"));
267
269
  if (pdf === undefined) {
268
270
  throw new Error(`couldn't find pdf for hash ${hash}`);
@@ -272,7 +274,7 @@ class Remarkable {
272
274
  }
273
275
  }
274
276
  async getEpub(hash) {
275
- const entries = await this.raw.getEntries(hash);
277
+ const { entries } = await this.raw.getEntries(hash);
276
278
  const [epub] = entries.filter((e) => e.id.endsWith(".epub"));
277
279
  if (epub === undefined) {
278
280
  throw new Error(`couldn't find epub for hash ${hash}`);
@@ -282,7 +284,7 @@ class Remarkable {
282
284
  }
283
285
  }
284
286
  async getDocument(hash) {
285
- const entries = await this.raw.getEntries(hash);
287
+ const { entries } = await this.raw.getEntries(hash);
286
288
  const zip = new JSZip();
287
289
  for (const entry of entries) {
288
290
  // TODO if this is .metadata we might want to assert type === "DocumentType"
@@ -331,7 +333,7 @@ class Remarkable {
331
333
  sizeInBytes: buffer.length.toFixed(),
332
334
  };
333
335
  // upload raw files, and get root hash
334
- const [[contentEntry, uploadContent], [metadataEntry, uploadMetadata], [pagedataEntry, uploadPagedata], [fileEntry, uploadFile], [rootHash, generation],] = await Promise.all([
336
+ const [[contentEntry, uploadContent], [metadataEntry, uploadMetadata], [pagedataEntry, uploadPagedata], [fileEntry, uploadFile], [rootHash, generation, schemaVersion],] = await Promise.all([
335
337
  this.raw.putContent(`${id}.content`, content),
336
338
  this.raw.putMetadata(`${id}.metadata`, metadata),
337
339
  // eslint-disable-next-line spellcheck/spell-checker
@@ -340,18 +342,13 @@ class Remarkable {
340
342
  this.#getRootHash(refresh),
341
343
  ]);
342
344
  // now fetch root entries and upload this file entry
343
- const [[collectionEntry, uploadCollection], rootEntries] = await Promise.all([
344
- this.raw.putEntries(id, [
345
- contentEntry,
346
- metadataEntry,
347
- pagedataEntry,
348
- fileEntry,
349
- ]),
345
+ const [[collectionEntry, uploadCollection], { entries: rootEntries }] = await Promise.all([
346
+ this.raw.putEntries(id, [contentEntry, metadataEntry, pagedataEntry, fileEntry], schemaVersion),
350
347
  this.raw.getEntries(rootHash),
351
348
  ]);
352
349
  // now upload a new root entry
353
350
  rootEntries.push(collectionEntry);
354
- const [rootEntry, uploadRoot] = await this.raw.putEntries("root", rootEntries);
351
+ const [rootEntry, uploadRoot] = await this.raw.putEntries("root", rootEntries, schemaVersion);
355
352
  // before updating the root hash, first upload everything
356
353
  await Promise.all([
357
354
  uploadContent,
@@ -393,19 +390,19 @@ class Remarkable {
393
390
  visibleName,
394
391
  };
395
392
  // upload folder contents
396
- const [[contentEntry, uploadContent], [metadataEntry, uploadMetadata], [rootHash, generation],] = await Promise.all([
393
+ const [[contentEntry, uploadContent], [metadataEntry, uploadMetadata], [rootHash, generation, schemaVersion],] = await Promise.all([
397
394
  this.raw.putContent(`${id}.content`, content),
398
395
  this.raw.putMetadata(`${id}.metadata`, metadata),
399
396
  this.#getRootHash(refresh),
400
397
  ]);
401
398
  // now fetch root entries and upload this file entry
402
- const [[collectionEntry, uploadCollection], rootEntries] = await Promise.all([
403
- this.raw.putEntries(id, [contentEntry, metadataEntry]),
399
+ const [[collectionEntry, uploadCollection], { entries: rootEntries }] = await Promise.all([
400
+ this.raw.putEntries(id, [contentEntry, metadataEntry], schemaVersion),
404
401
  this.raw.getEntries(rootHash),
405
402
  ]);
406
403
  // now upload a new root entry
407
404
  rootEntries.push(collectionEntry);
408
- const [rootEntry, uploadRoot] = await this.raw.putEntries("root", rootEntries);
405
+ const [rootEntry, uploadRoot] = await this.raw.putEntries("root", rootEntries, schemaVersion);
409
406
  // before updating the root hash, first upload everything
410
407
  await Promise.all([
411
408
  uploadContent,
@@ -430,8 +427,8 @@ class Remarkable {
430
427
  return await this.raw.uploadFile(visibleName, new Uint8Array(0), "folder");
431
428
  }
432
429
  /** edit just a content entry */
433
- async #editContentRaw(id, hash, update) {
434
- const entries = await this.raw.getEntries(hash);
430
+ async #editContentRaw(id, hash, update, schemaVersion) {
431
+ const { entries } = await this.raw.getEntries(hash);
435
432
  const contInd = entries.findIndex((ent) => ent.id.endsWith(".content"));
436
433
  const contEntry = entries[contInd];
437
434
  if (contEntry === undefined) {
@@ -441,28 +438,28 @@ class Remarkable {
441
438
  Object.assign(cont, update);
442
439
  const [newContEntry, uploadCont] = await this.raw.putContent(contEntry.id, cont);
443
440
  entries[contInd] = newContEntry;
444
- const [result, uploadEntries] = await this.raw.putEntries(id, entries);
441
+ const [result, uploadEntries] = await this.raw.putEntries(id, entries, schemaVersion);
445
442
  const upload = Promise.all([uploadCont, uploadEntries]);
446
443
  return [result, upload];
447
444
  }
448
445
  /** fully sync a content edit */
449
446
  async #editContent(hash, update, expectedType, refresh) {
450
- const [rootHash, generation] = await this.#getRootHash(refresh);
451
- const entries = await this.raw.getEntries(rootHash);
447
+ const [rootHash, generation, schemaVersion] = await this.#getRootHash(refresh);
448
+ const { entries } = await this.raw.getEntries(rootHash);
452
449
  const hashInd = entries.findIndex((ent) => ent.hash === hash);
453
450
  const hashEnt = entries[hashInd];
454
451
  if (hashEnt === undefined) {
455
452
  throw new HashNotFoundError(hash);
456
453
  }
457
454
  const [[newEnt, uploadEnt], meta] = await Promise.all([
458
- this.#editContentRaw(hashEnt.id, hash, update),
455
+ this.#editContentRaw(hashEnt.id, hash, update, schemaVersion),
459
456
  this.getMetadata(hash),
460
457
  ]);
461
458
  if (meta.type !== expectedType) {
462
459
  throw new Error(`expected type ${expectedType} but got ${meta.type} for hash ${hash}`);
463
460
  }
464
461
  entries[hashInd] = newEnt;
465
- const [rootEntry, uploadRoot] = await this.raw.putEntries("root", entries);
462
+ const [rootEntry, uploadRoot] = await this.raw.putEntries("root", entries, schemaVersion);
466
463
  await Promise.all([uploadEnt, uploadRoot]);
467
464
  await this.#putRootHash(rootEntry.hash, generation);
468
465
  return { hash: newEnt.hash };
@@ -479,8 +476,8 @@ class Remarkable {
479
476
  async updateTemplate(hash, content, refresh = false) {
480
477
  return await this.#editContent(hash, content, "TemplateType", refresh);
481
478
  }
482
- async #editMetaRaw(id, hash, update) {
483
- const entries = await this.raw.getEntries(hash);
479
+ async #editMetaRaw(id, hash, update, schemaVersion) {
480
+ const { entries } = await this.raw.getEntries(hash);
484
481
  const metaInd = entries.findIndex((ent) => ent.id.endsWith(".metadata"));
485
482
  const metaEntry = entries[metaInd];
486
483
  if (metaEntry === undefined) {
@@ -490,21 +487,21 @@ class Remarkable {
490
487
  Object.assign(meta, update);
491
488
  const [newMetaEntry, uploadMeta] = await this.raw.putMetadata(metaEntry.id, meta);
492
489
  entries[metaInd] = newMetaEntry;
493
- const [result, uploadEntries] = await this.raw.putEntries(id, entries);
490
+ const [result, uploadEntries] = await this.raw.putEntries(id, entries, schemaVersion);
494
491
  const upload = Promise.all([uploadMeta, uploadEntries]);
495
492
  return [result, upload];
496
493
  }
497
494
  async #editMeta(hash, update, refresh = false) {
498
- const [rootHash, generation] = await this.#getRootHash(refresh);
499
- const entries = await this.raw.getEntries(rootHash);
495
+ const [rootHash, generation, schemaVersion] = await this.#getRootHash(refresh);
496
+ const { entries } = await this.raw.getEntries(rootHash);
500
497
  const hashInd = entries.findIndex((ent) => ent.hash === hash);
501
498
  const hashEnt = entries[hashInd];
502
499
  if (hashEnt === undefined) {
503
500
  throw new HashNotFoundError(hash);
504
501
  }
505
- const [newEnt, uploadEnt] = await this.#editMetaRaw(hashEnt.id, hash, update);
502
+ const [newEnt, uploadEnt] = await this.#editMetaRaw(hashEnt.id, hash, update, schemaVersion);
506
503
  entries[hashInd] = newEnt;
507
- const [rootEntry, uploadRoot] = await this.raw.putEntries("root", entries);
504
+ const [rootEntry, uploadRoot] = await this.raw.putEntries("root", entries, schemaVersion);
508
505
  await Promise.all([uploadEnt, uploadRoot]);
509
506
  await this.#putRootHash(rootEntry.hash, generation);
510
507
  return { hash: newEnt.hash };
@@ -533,8 +530,8 @@ class Remarkable {
533
530
  if (!idReg.test(parent)) {
534
531
  throw new ValidationError(parent, idReg, "parent must be a valid document id");
535
532
  }
536
- const [rootHash, generation] = await this.#getRootHash(refresh);
537
- const entries = await this.raw.getEntries(rootHash);
533
+ const [rootHash, generation, schemaVersion] = await this.#getRootHash(refresh);
534
+ const { entries } = await this.raw.getEntries(rootHash);
538
535
  const hashSet = new Set(hashes);
539
536
  const toUpdate = [];
540
537
  const newEntries = [];
@@ -542,7 +539,7 @@ class Remarkable {
542
539
  const part = hashSet.has(entry.hash) ? toUpdate : newEntries;
543
540
  part.push(entry);
544
541
  }
545
- const resolved = await Promise.all(toUpdate.map(({ id, hash }) => this.#editMetaRaw(id, hash, { parent })));
542
+ const resolved = await Promise.all(toUpdate.map(({ id, hash }) => this.#editMetaRaw(id, hash, { parent }, schemaVersion)));
546
543
  const uploads = [];
547
544
  const result = {};
548
545
  for (const [i, [newEnt, upload]] of resolved.entries()) {
@@ -550,7 +547,7 @@ class Remarkable {
550
547
  uploads.push(upload);
551
548
  result[toUpdate[i].hash] = newEnt.hash;
552
549
  }
553
- const [rootEntry, uploadRoot] = await this.raw.putEntries("root", newEntries);
550
+ const [rootEntry, uploadRoot] = await this.raw.putEntries("root", newEntries, schemaVersion);
554
551
  await Promise.all([Promise.all(uploads), uploadRoot]);
555
552
  await this.#putRootHash(rootEntry.hash, generation);
556
553
  return { hashes: result };
@@ -571,7 +568,8 @@ class Remarkable {
571
568
  // should only go one step) to track all hashes encountered
572
569
  // NOTE that we could increase the cache in this process, or it's possible
573
570
  // for other calls to increase the cache with misc values.
574
- let entries = [await this.raw.getEntries(rootHash)];
571
+ const base = await this.raw.getEntries(rootHash);
572
+ let entries = [base.entries];
575
573
  let nextEntries = [];
576
574
  while (entries.length) {
577
575
  for (const entryList of entries) {
@@ -582,7 +580,8 @@ class Remarkable {
582
580
  }
583
581
  }
584
582
  }
585
- entries = await Promise.all(nextEntries);
583
+ const resolved = await Promise.all(nextEntries);
584
+ entries = resolved.map((ent) => ent.entries);
586
585
  nextEntries = [];
587
586
  }
588
587
  for (const key of toDelete) {
@@ -596,16 +595,13 @@ class Remarkable {
596
595
  }
597
596
  const cached = values(nullable(string()));
598
597
  /**
599
- * create an instance of the api
600
- *
601
- * This gets a temporary authentication token with the device token. If
602
- * requests start failing, simply recreate the api instance.
598
+ * Exchange a device token for a session token.
603
599
  *
604
600
  * @param deviceToken - the device token proving this api instance is
605
601
  * registered. Create one with {@link register}.
606
- * @returns an api instance
602
+ * @returns the session token returned by the reMarkable service
607
603
  */
608
- export async function remarkable(deviceToken, { authHost = AUTH_HOST, rawHost = RAW_HOST, uploadHost = UPLOAD_HOST, cache, maxCacheSize = Infinity, } = {}) {
604
+ export async function auth(deviceToken, { authHost = AUTH_HOST } = {}) {
609
605
  const resp = await fetch(`${authHost}/token/json/2/user/new`, {
610
606
  method: "POST",
611
607
  headers: {
@@ -615,16 +611,46 @@ export async function remarkable(deviceToken, { authHost = AUTH_HOST, rawHost =
615
611
  if (!resp.ok) {
616
612
  throw new Error(`couldn't fetch auth token: ${resp.statusText}`);
617
613
  }
618
- const userToken = await resp.text();
614
+ return await resp.text();
615
+ }
616
+ /**
617
+ * Create an API instance from an existing session token.
618
+ *
619
+ * If requests start failing, simply recreate the api instance with a freshly
620
+ * fetched session token.
621
+ *
622
+ * @param sessionToken - the session token used for authorization
623
+ * @returns an api instance
624
+ */
625
+ export function session(sessionToken, { rawHost = RAW_HOST, uploadHost = UPLOAD_HOST, cache, maxCacheSize = Infinity, } = {}) {
619
626
  const initCache = JSON.parse(cache ?? "{}");
620
627
  if (cached.guard(initCache)) {
621
628
  const entries = Object.entries(initCache);
622
- const cache = maxCacheSize === Infinity
629
+ const cacheMap = maxCacheSize === Infinity
623
630
  ? new Map(entries)
624
631
  : new LruCache(maxCacheSize, entries);
625
- return new Remarkable(userToken, rawHost, uploadHost, cache);
626
- }
627
- else {
628
- 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.");
632
+ return new Remarkable(sessionToken, rawHost, uploadHost, cacheMap);
629
633
  }
634
+ 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.");
635
+ }
636
+ /**
637
+ * create an instance of the api
638
+ *
639
+ * This gets a temporary authentication token with the device token and then
640
+ * constructs the api instance.
641
+ *
642
+ * @param deviceToken - the device token proving this api instance is
643
+ * registered. Create one with {@link register}.
644
+ * @returns an api instance
645
+ */
646
+ export async function remarkable(deviceToken, options = {}) {
647
+ const { authHost, rawHost, uploadHost, cache, maxCacheSize, syncHost } = options ?? {};
648
+ const sessionToken = await auth(deviceToken, { authHost });
649
+ return session(sessionToken, {
650
+ rawHost,
651
+ uploadHost,
652
+ cache,
653
+ maxCacheSize,
654
+ syncHost,
655
+ });
630
656
  }
package/dist/raw.d.ts CHANGED
@@ -1,7 +1,10 @@
1
+ import "core-js/proposals/array-buffer-base64";
1
2
  /** request types */
2
3
  export type RequestMethod = "POST" | "GET" | "PUT" | "DELETE" | "PATCH" | "OPTIONS";
3
4
  /** the supported upload mime types */
4
5
  export type UploadMimeType = "application/pdf" | "application/epub+zip" | "folder";
6
+ /** the schema version */
7
+ export type SchemaVersion = 3 | 4;
5
8
  /** an simple entry without any extra information */
6
9
  export interface SimpleEntry {
7
10
  /** the document id */
@@ -17,9 +20,9 @@ export interface SimpleEntry {
17
20
  * files, the high level entry will have the same hash and id as the low-level
18
21
  * entry for that collection.
19
22
  */
20
- export interface RawListEntry {
21
- /** collection type (80000000) */
22
- type: 80000000;
23
+ export interface RawEntry {
24
+ /** 80000000 for schema 3 collection type or 0 for schema 4 or schema 3 files or */
25
+ type: 80000000 | 0;
23
26
  /** the hash of the collection this points to */
24
27
  hash: string;
25
28
  /** the unique id of the collection */
@@ -29,23 +32,21 @@ export interface RawListEntry {
29
32
  /** the total size of everything in the collection */
30
33
  size: number;
31
34
  }
32
- /** the low-level entry for a single file */
33
- export interface RawFileEntry {
34
- /** file type (0) */
35
- type: 0;
36
- /** the hash of the file this points to */
37
- hash: string;
38
- /** the unique id of the file */
39
- id: string;
40
- /** the number of subfiles, always zero */
41
- subfiles: 0;
42
- /** the size of the file in bytes */
43
- size: number;
44
- }
45
- /** a low-level stored entry */
46
- export type RawEntry = RawListEntry | RawFileEntry;
47
35
  /** the type of files reMarkable supports */
48
36
  export type FileType = "epub" | "pdf" | "notebook";
37
+ /**
38
+ * a parsed entries file
39
+ *
40
+ * id and size are defined for schema 4 but not for 3
41
+ */
42
+ export interface Entries {
43
+ /** the raw entries in the file */
44
+ entries: RawEntry[];
45
+ /** the id of this entry, only specified for schema 4 */
46
+ id?: string;
47
+ /** the recursive size of this entry, only specified for schema 4 */
48
+ size?: number;
49
+ }
49
50
  /** a tag for an entry */
50
51
  export interface Tag {
51
52
  /** the name of the tag */
@@ -61,7 +62,7 @@ export interface PageTag extends Tag {
61
62
  /** all supported document orientations */
62
63
  export type Orientation = "portrait" | "landscape";
63
64
  /** all supported text alignments */
64
- export type TextAlignment = "justify" | "left";
65
+ export type TextAlignment = "" | "justify" | "left";
65
66
  /** types of zoom modes for documents, applies primarily to pdf files */
66
67
  export type ZoomMode = "bestFit" | "customFit" | "fitToHeight" | "fitToWidth";
67
68
  /**
@@ -202,8 +203,8 @@ export interface DocumentContent {
202
203
  pageCount: number;
203
204
  /** the page tags for the document */
204
205
  pageTags?: PageTag[];
205
- /** a list of the ids of each page in the document */
206
- pages?: string[];
206
+ /** a list of the ids of each page in the document, or null when never opened */
207
+ pages?: string[] | null;
207
208
  /** a mapping from page number to page id in pages */
208
209
  redirectionPageMap?: number[];
209
210
  /** ostensibly the size in bytes of the file, but this differs from other measurements */
@@ -426,7 +427,7 @@ export interface RawRemarkableApi {
426
427
  *
427
428
  * @returns the root hash and the current generation
428
429
  */
429
- getRootHash(): Promise<[string, number]>;
430
+ getRootHash(): Promise<[string, number, SchemaVersion]>;
430
431
  /**
431
432
  * get the raw binary data associated with a hash
432
433
  *
@@ -453,7 +454,7 @@ export interface RawRemarkableApi {
453
454
  * @param hash - the hash to get entries for
454
455
  * @returns the entries
455
456
  */
456
- getEntries(hash: string): Promise<RawEntry[]>;
457
+ getEntries(hash: string): Promise<Entries>;
457
458
  /**
458
459
  * get the parsed and validated `Content` of a content hash
459
460
  *
@@ -508,13 +509,13 @@ export interface RawRemarkableApi {
508
509
  * @param bytes - the bytes to upload
509
510
  * @returns the new entry and a promise to finish the upload
510
511
  */
511
- putFile(id: string, bytes: Uint8Array): Promise<[RawFileEntry, Promise<void>]>;
512
+ putFile(id: string, bytes: Uint8Array): Promise<[RawEntry, Promise<void>]>;
512
513
  /** the same as {@link putFile | `putFile`} but with caching for text */
513
- putText(id: string, content: string): Promise<[RawFileEntry, Promise<void>]>;
514
+ putText(id: string, content: string): Promise<[RawEntry, Promise<void>]>;
514
515
  /** the same as {@link putText | `putText`} but with extra validation for Content */
515
- putContent(id: string, content: Content): Promise<[RawFileEntry, Promise<void>]>;
516
+ putContent(id: string, content: Content): Promise<[RawEntry, Promise<void>]>;
516
517
  /** the same as {@link putText | `putText`} but with extra validation for Metadata */
517
- putMetadata(id: string, metadata: Metadata): Promise<[RawFileEntry, Promise<void>]>;
518
+ putMetadata(id: string, metadata: Metadata): Promise<[RawEntry, Promise<void>]>;
518
519
  /**
519
520
  * put a set of entries to make an entry list file
520
521
  *
@@ -530,7 +531,7 @@ export interface RawRemarkableApi {
530
531
  *
531
532
  * @returns the new list entry and a promise to finish the upload
532
533
  */
533
- putEntries(id: string, entries: RawEntry[]): Promise<[RawListEntry, Promise<void>]>;
534
+ putEntries(id: string, entries: RawEntry[], schemaVersion: SchemaVersion): Promise<[RawEntry, Promise<void>]>;
534
535
  /**
535
536
  * upload a file to the reMarkable cloud using the simple api
536
537
  *
@@ -554,28 +555,26 @@ export interface RawRemarkableApi {
554
555
  /** completely clear the cache */
555
556
  clearCache(): void;
556
557
  }
557
- interface AuthedFetch {
558
- (method: RequestMethod, url: string, init?: {
559
- body?: string | Uint8Array;
560
- headers?: Record<string, string>;
561
- }): Promise<Response>;
562
- }
558
+ type AuthedFetch = (method: RequestMethod, url: string, init?: {
559
+ body?: string | Uint8Array;
560
+ headers?: Record<string, string>;
561
+ }) => Promise<Response>;
563
562
  export declare class RawRemarkable implements RawRemarkableApi {
564
563
  #private;
565
564
  constructor(authedFetch: AuthedFetch, cache: Map<string, string | null>, rawHost: string, uploadHost: string);
566
565
  /** make an authorized request to remarkable */
567
- getRootHash(): Promise<[string, number]>;
566
+ getRootHash(): Promise<[string, number, SchemaVersion]>;
568
567
  getHash(hash: string): Promise<Uint8Array>;
569
568
  getText(hash: string): Promise<string>;
570
- getEntries(hash: string): Promise<RawEntry[]>;
569
+ getEntries(hash: string): Promise<Entries>;
571
570
  getContent(hash: string): Promise<Content>;
572
571
  getMetadata(hash: string): Promise<Metadata>;
573
572
  putRootHash(hash: string, generation: number, broadcast?: boolean): Promise<[string, number]>;
574
- putFile(id: string, bytes: Uint8Array): Promise<[RawFileEntry, Promise<void>]>;
575
- putText(id: string, text: string): Promise<[RawFileEntry, Promise<void>]>;
576
- putContent(id: string, content: Content): Promise<[RawFileEntry, Promise<void>]>;
577
- putMetadata(id: string, metadata: Metadata): Promise<[RawFileEntry, Promise<void>]>;
578
- putEntries(id: string, entries: RawEntry[]): Promise<[RawListEntry, Promise<void>]>;
573
+ putFile(id: string, bytes: Uint8Array): Promise<[RawEntry, Promise<void>]>;
574
+ putText(id: string, text: string): Promise<[RawEntry, Promise<void>]>;
575
+ putContent(id: string, content: Content): Promise<[RawEntry, Promise<void>]>;
576
+ putMetadata(id: string, metadata: Metadata): Promise<[RawEntry, Promise<void>]>;
577
+ putEntries(id: string, entries: RawEntry[], schemaVersion: SchemaVersion): Promise<[RawEntry, Promise<void>]>;
579
578
  uploadFile(visibleName: string, bytes: Uint8Array, mime: UploadMimeType): Promise<SimpleEntry>;
580
579
  dumpCache(): string;
581
580
  clearCache(): void;