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/dist/raw.js CHANGED
@@ -1,7 +1,8 @@
1
- import { fromByteArray } from "base64-js";
2
1
  import CRC32C from "crc-32/crc32c";
3
- import { boolean, elements, empty, enumeration, float64, int32, properties, string, timestamp, uint32, uint8, values, } from "jtd-ts";
2
+ import { boolean, elements, empty, enumeration, float64, int32, nullable, properties, string, timestamp, uint8, uint32, values, } from "jtd-ts";
4
3
  import { ValidationError } from "./error.js";
4
+ import { concatArrays } from "./utils.js";
5
+ import "core-js/proposals/array-buffer-base64";
5
6
  const hashReg = /^[0-9a-f]{64}$/;
6
7
  const tag = properties({
7
8
  name: string(),
@@ -74,7 +75,7 @@ const documentContent = properties({
74
75
  orientation: enumeration("portrait", "landscape"),
75
76
  pageCount: uint32(),
76
77
  sizeInBytes: string(),
77
- textAlignment: enumeration("justify", "left"),
78
+ textAlignment: enumeration("", "justify", "left"),
78
79
  textScale: float64(),
79
80
  }, {
80
81
  cPages,
@@ -90,10 +91,10 @@ const documentContent = properties({
90
91
  count: uint32(),
91
92
  timestamp: float64(),
92
93
  }, undefined, true),
93
- lastOpenedPage: uint32(),
94
+ lastOpenedPage: int32(),
94
95
  margins: uint32(),
95
96
  originalPageCount: int32(),
96
- pages: elements(string()),
97
+ pages: nullable(elements(string())),
97
98
  pageTags: elements(pageTag),
98
99
  redirectionPageMap: elements(int32()),
99
100
  tags: elements(tag),
@@ -134,7 +135,7 @@ const metadata = properties({
134
135
  visibleName: string(),
135
136
  }, {
136
137
  lastOpened: string(),
137
- lastOpenedPage: uint32(),
138
+ lastOpenedPage: int32(),
138
139
  createdTime: string(),
139
140
  deleted: boolean(),
140
141
  metadatamodified: boolean(),
@@ -159,9 +160,29 @@ async function digest(buff) {
159
160
  const digest = await crypto.subtle.digest("SHA-256",
160
161
  // NOTE this is type hinted wrong, but it does work correctly on a uint8 view
161
162
  buff);
162
- return [...new Uint8Array(digest)]
163
- .map((x) => x.toString(16).padStart(2, "0"))
164
- .join("");
163
+ return new Uint8Array(digest).toHex();
164
+ }
165
+ function parseRawEntryLine(line) {
166
+ const [hash, type, id, subfiles, size] = line.split(":");
167
+ if (hash === undefined ||
168
+ type === undefined ||
169
+ id === undefined ||
170
+ subfiles === undefined ||
171
+ size === undefined) {
172
+ throw new Error(`line '${line}' was not formatted correctly`);
173
+ }
174
+ else if (type === "80000000" || type === "0") {
175
+ return {
176
+ hash,
177
+ type: type === "0" ? 0 : 80000000,
178
+ id,
179
+ subfiles: parseInt(subfiles, 10),
180
+ size: parseInt(size, 10),
181
+ };
182
+ }
183
+ else {
184
+ throw new Error(`line '${line}' was not formatted correctly`);
185
+ }
165
186
  }
166
187
  export class RawRemarkable {
167
188
  #authedFetch;
@@ -191,14 +212,14 @@ export class RawRemarkable {
191
212
  if (!rootHash.guardAssert(loaded))
192
213
  throw Error("invalid root hash");
193
214
  const { hash, generation, schemaVersion } = loaded;
194
- if (schemaVersion !== 3) {
215
+ if (schemaVersion !== 3 && schemaVersion !== 4) {
195
216
  throw new Error(`schema version ${schemaVersion} not supported`);
196
217
  }
197
218
  else if (!Number.isSafeInteger(generation)) {
198
219
  throw new Error(`generation ${generation} was not a safe integer; please file a bug report`);
199
220
  }
200
221
  else {
201
- return [hash, generation];
222
+ return [hash, generation, schemaVersion];
202
223
  }
203
224
  }
204
225
  async #getHash(hash) {
@@ -243,41 +264,30 @@ export class RawRemarkable {
243
264
  async getEntries(hash) {
244
265
  const rawFile = await this.getText(hash);
245
266
  const [version, ...rest] = rawFile.slice(0, -1).split("\n");
246
- if (version != "3") {
247
- throw new Error(`schema version ${version} not supported`);
267
+ if (version === "3") {
268
+ return { entries: rest.map(parseRawEntryLine) };
269
+ }
270
+ else if (version === "4") {
271
+ const [info, ...remaining] = rest;
272
+ if (!info)
273
+ throw new Error("missing info line for schema version 4");
274
+ const [lead, id, count, size] = info.split(":");
275
+ if (lead !== "0" ||
276
+ id === undefined ||
277
+ count === undefined ||
278
+ size === undefined) {
279
+ throw new Error(`schema 4 info line '${info}' was not formatted correctly`);
280
+ }
281
+ const entries = remaining.map(parseRawEntryLine);
282
+ if (parseInt(count, 10) !== entries.length) {
283
+ throw new Error(`schema 4 expected ${count} entries, but found ${entries.length}`);
284
+ }
285
+ else {
286
+ return { entries, id, size: parseInt(size, 10) };
287
+ }
248
288
  }
249
289
  else {
250
- return rest.map((line) => {
251
- const [hash, type, id, subfiles, size] = line.split(":");
252
- if (hash === undefined ||
253
- type === undefined ||
254
- id === undefined ||
255
- subfiles === undefined ||
256
- size === undefined) {
257
- throw new Error(`line '${line}' was not formatted correctly`);
258
- }
259
- else if (type === "80000000") {
260
- return {
261
- hash,
262
- type: 80000000,
263
- id,
264
- subfiles: parseInt(subfiles),
265
- size: parseInt(size),
266
- };
267
- }
268
- else if (type === "0" && subfiles === "0") {
269
- return {
270
- hash,
271
- type: 0,
272
- id,
273
- subfiles: 0,
274
- size: parseInt(size),
275
- };
276
- }
277
- else {
278
- throw new Error(`line '${line}' was not formatted correctly`);
279
- }
280
- });
290
+ throw new Error(`schema version ${version} not supported`);
281
291
  }
282
292
  }
283
293
  async getContent(hash) {
@@ -341,7 +351,7 @@ export class RawRemarkable {
341
351
  const crc = CRC32C.buf(bytes, 0);
342
352
  const buff = new ArrayBuffer(4);
343
353
  new DataView(buff).setInt32(0, crc, false);
344
- const crcHash = fromByteArray(new Uint8Array(buff));
354
+ const crcHash = new Uint8Array(buff).toBase64();
345
355
  await this.#authedFetch("PUT", `${this.#rawHost}/sync/v3/files/${hash}`, {
346
356
  body: bytes,
347
357
  headers: {
@@ -396,39 +406,55 @@ export class RawRemarkable {
396
406
  return await this.putText(id, JSON.stringify(metadata));
397
407
  }
398
408
  }
399
- async putEntries(id, entries) {
400
- // NOTE collections have a special hash function, the hash of their
409
+ async putEntries(id, entries, schemaVersion) {
410
+ // NOTE v3 collections have a special hash function, the hash of their
401
411
  // contents, so this needs to be different
402
412
  entries.sort((a, b) => a.id.localeCompare(b.id));
403
- const hashBuff = new Uint8Array(entries.length * 32);
404
- for (const [start, { hash }] of entries.entries()) {
405
- for (const [i, byte] of (hash.match(/../g) ?? []).entries()) {
406
- hashBuff[start * 32 + i] = parseInt(byte, 16);
407
- }
408
- }
409
- const hash = await digest(hashBuff);
410
413
  const size = entries.reduce((acc, ent) => acc + ent.size, 0);
411
- const records = ["3\n"];
414
+ const records = [`${schemaVersion}\n`];
415
+ if (schemaVersion === 4) {
416
+ const name = id === "root" ? "." : id;
417
+ records.push(`0:${name}:${entries.length}:${size}\n`);
418
+ }
412
419
  for (const { hash, type, id, subfiles, size } of entries) {
413
420
  records.push(`${hash}:${type}:${id}:${subfiles}:${size}\n`);
414
421
  }
422
+ const enc = new TextEncoder();
423
+ const entryBuff = enc.encode(records.join(""));
424
+ let hash;
425
+ if (schemaVersion === 3) {
426
+ // in schema version 3 an entry's hash is the hash of the concatenated hashes
427
+ const hashBuffs = [];
428
+ for (const { hash } of entries) {
429
+ hashBuffs.push(Uint8Array.fromHex(hash));
430
+ }
431
+ hash = await digest(concatArrays(hashBuffs));
432
+ }
433
+ else if (schemaVersion === 4) {
434
+ // in schema version 4 an entry's hash is the hash of the full entry file, same as everything else
435
+ hash = await digest(entryBuff);
436
+ }
437
+ else {
438
+ throw new Error(`unsupported schema version ${schemaVersion}`);
439
+ }
415
440
  const res = {
416
441
  id,
417
442
  hash,
418
- type: 80000000,
443
+ type: schemaVersion > 3 ? 0 : 80000000,
419
444
  subfiles: entries.length,
420
445
  size,
421
446
  };
422
- const enc = new TextEncoder();
423
447
  return [
424
448
  res,
425
449
  // NOTE when monitoring requests, this had the extension .docSchema appended, but I'm not entirely sure why
426
- this.#putFile(hash, `${id}.docSchema`, enc.encode(records.join(""))),
450
+ this.#putFile(hash, `${id}.docSchema`, entryBuff),
427
451
  ];
428
452
  }
429
453
  async uploadFile(visibleName, bytes, mime) {
430
454
  const enc = new TextEncoder();
431
- const meta = fromByteArray(enc.encode(JSON.stringify({ file_name: visibleName })));
455
+ const meta = enc
456
+ .encode(JSON.stringify({ file_name: visibleName }))
457
+ .toBase64();
432
458
  const resp = await this.#authedFetch("POST", `${this.#uploadHost}/doc/v2/files`, {
433
459
  body: bytes,
434
460
  headers: {