rmapi-js 10.2.0 → 11.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.
@@ -0,0 +1,22 @@
1
+ /** a reMarkable device display */
2
+ export interface DeviceScreen {
3
+ /** the marketing name */
4
+ name: string;
5
+ /** native portrait width in pixels */
6
+ width: number;
7
+ /** native portrait height in pixels */
8
+ height: number;
9
+ /** display density in dots per inch */
10
+ dpi: number;
11
+ }
12
+ /** the model number of a known reMarkable device */
13
+ export type DeviceModel = "RM100" | "RM110" | "RM02A" | "RM03A" | "RM102";
14
+ /**
15
+ * display specs for known reMarkable devices, keyed by model number
16
+ *
17
+ * These feed the `customFit` zoom math: `customZoomPageWidth`/`customZoomPageHeight`
18
+ * are the source page in device pixels (`pagePt * dpi / 72`), and `width`/`height`
19
+ * give the screen aspect. Every model is 3:4 (0.75) except the Paper Pro Move,
20
+ * which is 9:16.
21
+ */
22
+ export declare const deviceScreens: Record<DeviceModel, DeviceScreen>;
@@ -0,0 +1,20 @@
1
+ /**
2
+ * display specs for known reMarkable devices, keyed by model number
3
+ *
4
+ * These feed the `customFit` zoom math: `customZoomPageWidth`/`customZoomPageHeight`
5
+ * are the source page in device pixels (`pagePt * dpi / 72`), and `width`/`height`
6
+ * give the screen aspect. Every model is 3:4 (0.75) except the Paper Pro Move,
7
+ * which is 9:16.
8
+ */
9
+ export const deviceScreens = {
10
+ RM100: { name: "reMarkable 1", width: 1404, height: 1872, dpi: 226 },
11
+ RM110: { name: "reMarkable 2", width: 1404, height: 1872, dpi: 226 },
12
+ RM02A: { name: "reMarkable Paper Pro", width: 1620, height: 2160, dpi: 229 },
13
+ RM03A: {
14
+ name: "reMarkable Paper Pro Move",
15
+ width: 954,
16
+ height: 1696,
17
+ dpi: 264,
18
+ },
19
+ RM102: { name: "reMarkable Paper Pure", width: 1404, height: 1872, dpi: 226 },
20
+ };
@@ -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,4 +1,5 @@
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.js";
2
+ export { type DeviceModel, type DeviceScreen, deviceScreens, } from "./devices.js";
2
3
  export { HashNotFoundError, ValidationError } from "./error.js";
3
4
  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
5
  /** common properties shared by collections and documents */
@@ -162,6 +163,18 @@ export interface PutOptions {
162
163
  zoomMode?: ZoomMode;
163
164
  /** the contrast filter setting */
164
165
  viewBackgroundFilter?: BackgroundFilter;
166
+ /** the custom zoom scale, applied when zoomMode is "customFit" */
167
+ customZoomScale?: number;
168
+ /** the horizontal center offset for customFit zoom */
169
+ customZoomCenterX?: number;
170
+ /** the vertical center offset for customFit zoom */
171
+ customZoomCenterY?: number;
172
+ /** the rendered page width in pixels, the unit customFit centers use */
173
+ customZoomPageWidth?: number;
174
+ /** the rendered page height in pixels, the unit customFit centers use */
175
+ customZoomPageHeight?: number;
176
+ /** the orientation the customFit zoom was set in */
177
+ customZoomOrientation?: Orientation;
165
178
  /**
166
179
  * whether to refresh current file structure before putting
167
180
  *
@@ -296,6 +309,28 @@ export interface RemarkableApi {
296
309
  * doesn't match the current server generation, requiring you to retry until
297
310
  * it works.
298
311
  *
312
+ * @remarks
313
+ * When `zoomMode` is `"customFit"` the `customZoom*` fields describe the view,
314
+ * all in the source page's device pixels: `customZoomPageWidth` and
315
+ * `customZoomPageHeight` are the page dimensions scaled by the device dpi
316
+ * (`pagePt * dpi / 72`, see {@link deviceScreens | `deviceScreens`}), and the
317
+ * centers are in those pixels.
318
+ *
319
+ * The view always has the device's aspect ratio — you control its height and
320
+ * position, not its shape. `customZoomScale` is a fit-to-height zoom: at `1`
321
+ * the page height fills the screen, and the linear magnification is
322
+ * `customZoomScale` squared. `customZoomCenterX` offsets the center of the
323
+ * view horizontally from the page center, and `customZoomCenterY` is the
324
+ * absolute distance of the center down from the top of the page; the view's
325
+ * width follows from its height and the device aspect ratio.
326
+ *
327
+ * The fields are a single document-wide setting, but `customZoomCenterY` is
328
+ * applied against each page's own rendered height. On a page rendered taller
329
+ * than `customZoomPageHeight` that distance is a smaller fraction of the page,
330
+ * so the view sits higher and cuts off the bottom; on a shorter page it sits
331
+ * lower and cuts off the top. `customZoomScale` (a ratio) and
332
+ * `customZoomCenterX` (an offset from center) do not shift with page size.
333
+ *
299
334
  * @param visibleName - the name to display on the reMarkable
300
335
  * @param buffer - the raw pdf
301
336
  * @param opts - put options
package/dist/index.js CHANGED
@@ -57,13 +57,11 @@ import { z } from "zod";
57
57
  import { HashNotFoundError, ValidationError } from "./error.js";
58
58
  import { LruCache } from "./lru.js";
59
59
  import { RawRemarkable, } from "./raw.js";
60
+ export { deviceScreens, } from "./devices.js";
60
61
  export { HashNotFoundError, ValidationError } from "./error.js";
61
62
  const AUTH_HOST = "https://webapp-prod.cloud.remarkable.engineering";
62
63
  const RAW_HOST = "https://eu.tectonic.remarkable.com";
63
64
  const UPLOAD_HOST = "https://internal.cloud.remarkable.com";
64
- // ------------ //
65
- // Request Info //
66
- // ------------ //
67
65
  // The section has all the types that are stored in the remarkable cloud.
68
66
  const idReg = /^([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}||trash)$/;
69
67
  /** An error that gets thrown when the backend while trying to update
@@ -295,7 +293,7 @@ class Remarkable {
295
293
  }
296
294
  return zip.generateAsync({ type: "uint8array" });
297
295
  }
298
- async #putFile(visibleName, fileType, buffer, { refresh, parent = "", pinned = false, zoomMode = "bestFit", viewBackgroundFilter, textScale = 1, textAlignment = "justify", fontName = "", coverPageNumber = -1, authors, title, publicationDate, publisher, extraMetadata = {}, lineHeight = -1, margins = 125, orientation = "portrait", tags, }) {
296
+ async #putFile(visibleName, fileType, buffer, { refresh, parent = "", pinned = false, zoomMode = "bestFit", viewBackgroundFilter, textScale = 1, textAlignment = "justify", fontName = "", coverPageNumber = -1, authors, title, publicationDate, publisher, extraMetadata = {}, lineHeight = -1, margins = 125, orientation = "portrait", tags, customZoomScale, customZoomCenterX, customZoomCenterY, customZoomPageWidth, customZoomPageHeight, customZoomOrientation, }) {
299
297
  if (parent && !idReg.test(parent)) {
300
298
  throw new ValidationError(parent, idReg, "parent must be a valid document id");
301
299
  }
@@ -325,6 +323,12 @@ class Remarkable {
325
323
  textAlignment,
326
324
  textScale,
327
325
  zoomMode,
326
+ customZoomScale,
327
+ customZoomCenterX,
328
+ customZoomCenterY,
329
+ customZoomPageWidth,
330
+ customZoomPageHeight,
331
+ customZoomOrientation,
328
332
  viewBackgroundFilter,
329
333
  // NOTE for some reason we need to "fake" the number of pages at 1, and
330
334
  // create "valid" output for that
@@ -339,7 +343,6 @@ class Remarkable {
339
343
  const [[contentEntry, uploadContent], [metadataEntry, uploadMetadata], [pagedataEntry, uploadPagedata], [fileEntry, uploadFile], [rootHash, generation, schemaVersion],] = await Promise.all([
340
344
  this.raw.putContent(`${id}.content`, content),
341
345
  this.raw.putMetadata(`${id}.metadata`, metadata),
342
- // eslint-disable-next-line spellcheck/spell-checker
343
346
  this.raw.putText(`${id}.pagedata`, "\n"),
344
347
  this.raw.putFile(`${id}.${fileType}`, buffer),
345
348
  this.#getRootHash(refresh),
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 */
@@ -227,38 +226,43 @@ export interface CommonDocumentContent {
227
226
  */
228
227
  textScale: number;
229
228
  /**
230
- * the center of the zoom for customFit zoom
229
+ * the horizontal center of a customFit zoom
231
230
  *
232
- * This is an absolute offset from the center of the page. Negative numbers
233
- * indicate shifted left and positive numbers indicate shifted right. The
234
- * units are relative to the document pixels, but it's not sure how the
235
- * document size is calculated.
231
+ * An offset in device pixels from the horizontal center of the page (0 =
232
+ * centered, negative = left, positive = right). This and
233
+ * {@link customZoomCenterY} are in the page's own frame, so device
234
+ * orientation does not affect them.
236
235
  */
237
236
  customZoomCenterX?: number;
238
237
  /**
239
- * the center of the zoom for customFit documents
238
+ * the vertical center of a customFit zoom
240
239
  *
241
- * This is an absolute number relative to the top of the page. Negative
242
- * numbers indicate shifted up, while positive numbers indicate shifted down.
243
- * The units are relative to the document pixels, but it's not sure how the
244
- * document size is calculated.
240
+ * An absolute distance in device pixels from the top of the page (negative =
241
+ * up, positive = down); centering is half the page's rendered height.
245
242
  */
246
243
  customZoomCenterY?: number;
247
- /** this seems unused */
244
+ /** the orientation the customFit zoom was set in */
248
245
  customZoomOrientation?: Orientation;
249
- /** this seems unused */
246
+ /**
247
+ * the rendered height of the pdf page, in device pixels
248
+ *
249
+ * Computed from the pdf page height in points and the device dpi as
250
+ * `heightPt * dpi / 72`; the dpi depends on the model (see
251
+ * {@link deviceScreens | `deviceScreens`}).
252
+ */
250
253
  customZoomPageHeight?: number;
251
- /** this seems unused */
254
+ /** the rendered width of the pdf page, in device pixels */
252
255
  customZoomPageWidth?: number;
253
256
  /**
254
- * the scale for customFit documents
257
+ * the scale for a customFit zoom
255
258
  *
256
- * 1 indicates no zoom, smaller numbers indicate zoomed out, larger numbers
257
- * indicate zoomed in. reMarkable generally allows setting this from 0.5 to 5,
258
- * but values outside that bound are still supported.
259
+ * Calibrated on the page's fit-to-height: at 1 the rendered page height fills
260
+ * the screen and the linear magnification is customZoomScale squared. 1
261
+ * indicates no zoom; reMarkable generally allows 0.5 to 5, but values outside
262
+ * that bound are still supported.
259
263
  */
260
264
  customZoomScale?: number;
261
- /** what zoom mode is set for the page */
265
+ /** the zoom mode; customFit applies the customZoom* fields, the rest auto-fit */
262
266
  zoomMode?: ZoomMode;
263
267
  /** [speculative] a transform matrix, a. la. css matrix transform */
264
268
  transform?: Partial<Record<`m${"1" | "2" | "3"}${"1" | "2" | "3"}`, number>>;
package/dist/raw.js CHANGED
@@ -2,7 +2,6 @@ import CRC32C from "crc-32/crc32c";
2
2
  import { z } from "zod";
3
3
  import { ValidationError } from "./error.js";
4
4
  import { concatArrays } from "./utils.js";
5
- import "core-js/proposals/array-buffer-base64";
6
5
  const hashReg = /^[0-9a-f]{64}$/;
7
6
  const tag = z
8
7
  .object({
@@ -127,7 +126,6 @@ const documentContentOptional = {
127
126
  })
128
127
  .passthrough()
129
128
  .optional(),
130
- // eslint-disable-next-line spellcheck/spell-checker
131
129
  viewBackgroundFilter: z.enum(["off", "fullpage"]).optional(),
132
130
  zoomMode: z
133
131
  .enum(["bestFit", "customFit", "fitToHeight", "fitToWidth"])
@@ -380,7 +378,6 @@ export class RawRemarkable {
380
378
  body: bytes,
381
379
  headers: {
382
380
  "rm-filename": fileName,
383
- // eslint-disable-next-line spellcheck/spell-checker
384
381
  "x-goog-hash": `crc32c=${crcHash}`,
385
382
  },
386
383
  });