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 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 api = session(sessionToken);
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 pdf, using the hash (from listItems)
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
@@ -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 { HashNotFoundError, ValidationError } from "./error";
58
- import { LruCache } from "./lru";
59
- import { RawRemarkable, } from "./raw";
60
- export { HashNotFoundError, ValidationError } from "./error";
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
- this.#lastHashGen = await this.raw.getRootHash();
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
- const [, , schemaVersion] = this.#lastHashGen; // guaranteed to be set
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
- // the keys to delete, we'll drop every key we can currently reach
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, type, id } of entryList) {
577
- toDelete.add(hash);
578
- if (type === 80000000) {
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 = values(nullable(string()));
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
- if (cached.guard(initCache)) {
628
- const entries = Object.entries(initCache);
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, syncHost } = options ?? {};
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;