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 +3 -3
- package/dist/error.d.ts +14 -0
- package/dist/error.js +21 -0
- package/dist/index.d.ts +3 -9
- package/dist/index.js +25 -26
- package/dist/lru.d.ts +8 -0
- package/dist/lru.js +64 -0
- package/dist/raw.d.ts +2 -3
- package/dist/raw.js +206 -200
- package/dist/rmapi-js.esm.min.js +9 -23
- package/dist/utils.d.ts +1 -0
- package/dist/utils.js +10 -0
- package/package.json +14 -9
package/dist/raw.js
CHANGED
|
@@ -1,172 +1,207 @@
|
|
|
1
1
|
import CRC32C from "crc-32/crc32c";
|
|
2
|
-
import {
|
|
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
|
-
const tag =
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
timestamp: string(),
|
|
43
|
-
value:
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
timestamp: string(),
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
6
|
+
const tag = z
|
|
7
|
+
.object({
|
|
8
|
+
name: z.string(),
|
|
9
|
+
timestamp: z.number(),
|
|
10
|
+
})
|
|
11
|
+
.passthrough();
|
|
12
|
+
const pageTag = z
|
|
13
|
+
.object({
|
|
14
|
+
name: z.string(),
|
|
15
|
+
pageId: z.string(),
|
|
16
|
+
timestamp: z.number(),
|
|
17
|
+
})
|
|
18
|
+
.passthrough();
|
|
19
|
+
const documentMetadata = z
|
|
20
|
+
.object({
|
|
21
|
+
authors: z.array(z.string()).optional(),
|
|
22
|
+
title: z.string().optional(),
|
|
23
|
+
publicationDate: z.string().optional(),
|
|
24
|
+
publisher: z.string().optional(),
|
|
25
|
+
})
|
|
26
|
+
.passthrough();
|
|
27
|
+
const cPagePage = z
|
|
28
|
+
.object({
|
|
29
|
+
id: z.string(),
|
|
30
|
+
idx: z.object({ timestamp: z.string(), value: z.string() }).passthrough(),
|
|
31
|
+
template: z
|
|
32
|
+
.object({ timestamp: z.string(), value: z.string() })
|
|
33
|
+
.passthrough()
|
|
34
|
+
.optional(),
|
|
35
|
+
redir: z
|
|
36
|
+
.object({ timestamp: z.string(), value: z.number().int() })
|
|
37
|
+
.passthrough()
|
|
38
|
+
.optional(),
|
|
39
|
+
scrollTime: z
|
|
40
|
+
.object({
|
|
41
|
+
timestamp: z.string(),
|
|
42
|
+
value: z.string().datetime({ offset: true }),
|
|
43
|
+
})
|
|
44
|
+
.passthrough()
|
|
45
|
+
.optional(),
|
|
46
|
+
verticalScroll: z
|
|
47
|
+
.object({ timestamp: z.string(), value: z.number() })
|
|
48
|
+
.passthrough()
|
|
49
|
+
.optional(),
|
|
50
|
+
deleted: z
|
|
51
|
+
.object({ timestamp: z.string(), value: z.number().int() })
|
|
52
|
+
.passthrough()
|
|
53
|
+
.optional(),
|
|
54
|
+
})
|
|
55
|
+
.passthrough();
|
|
56
|
+
const cPages = z
|
|
57
|
+
.object({
|
|
58
|
+
lastOpened: z
|
|
59
|
+
.object({ timestamp: z.string(), value: z.string() })
|
|
60
|
+
.passthrough(),
|
|
61
|
+
original: z
|
|
62
|
+
.object({ timestamp: z.string(), value: z.number().int() })
|
|
63
|
+
.passthrough(),
|
|
64
|
+
pages: z.array(cPagePage),
|
|
65
|
+
uuids: z
|
|
66
|
+
.array(z
|
|
67
|
+
.object({ first: z.string(), second: z.number().int().nonnegative() })
|
|
68
|
+
.passthrough())
|
|
69
|
+
.nullable(),
|
|
70
|
+
})
|
|
71
|
+
.passthrough();
|
|
72
|
+
const collectionContent = z
|
|
73
|
+
.object({
|
|
74
|
+
tags: z.array(tag).optional(),
|
|
75
|
+
})
|
|
76
|
+
.strict();
|
|
77
|
+
const legacyCollectionContent = z
|
|
78
|
+
.object({
|
|
79
|
+
tags: z.array(z.string()).optional(),
|
|
80
|
+
})
|
|
81
|
+
.strict();
|
|
71
82
|
const documentContentRequired = {
|
|
72
|
-
coverPageNumber:
|
|
83
|
+
coverPageNumber: z.number().int(),
|
|
73
84
|
documentMetadata,
|
|
74
|
-
extraMetadata:
|
|
75
|
-
fileType:
|
|
76
|
-
fontName: string(),
|
|
77
|
-
lineHeight:
|
|
78
|
-
orientation:
|
|
79
|
-
pageCount:
|
|
80
|
-
textAlignment:
|
|
81
|
-
textScale:
|
|
85
|
+
extraMetadata: z.record(z.string(), z.string()),
|
|
86
|
+
fileType: z.enum(["epub", "notebook", "pdf"]),
|
|
87
|
+
fontName: z.string(),
|
|
88
|
+
lineHeight: z.number().int(),
|
|
89
|
+
orientation: z.enum(["portrait", "landscape"]),
|
|
90
|
+
pageCount: z.number().int().nonnegative(),
|
|
91
|
+
textAlignment: z.enum(["", "justify", "left"]),
|
|
92
|
+
textScale: z.number(),
|
|
82
93
|
};
|
|
83
94
|
const documentContentOptional = {
|
|
84
|
-
cPages,
|
|
85
|
-
customZoomCenterX:
|
|
86
|
-
customZoomCenterY:
|
|
87
|
-
customZoomOrientation:
|
|
88
|
-
customZoomPageHeight:
|
|
89
|
-
customZoomPageWidth:
|
|
90
|
-
customZoomScale:
|
|
91
|
-
dummyDocument: boolean(),
|
|
92
|
-
formatVersion:
|
|
93
|
-
keyboardMetadata:
|
|
94
|
-
count:
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
lastOpenedPage:
|
|
98
|
-
margins:
|
|
99
|
-
originalPageCount:
|
|
100
|
-
pages:
|
|
101
|
-
pageTags:
|
|
102
|
-
redirectionPageMap:
|
|
103
|
-
sizeInBytes: string(),
|
|
104
|
-
transform:
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
95
|
+
cPages: cPages.optional(),
|
|
96
|
+
customZoomCenterX: z.number().optional(),
|
|
97
|
+
customZoomCenterY: z.number().optional(),
|
|
98
|
+
customZoomOrientation: z.enum(["portrait", "landscape"]).optional(),
|
|
99
|
+
customZoomPageHeight: z.number().optional(),
|
|
100
|
+
customZoomPageWidth: z.number().optional(),
|
|
101
|
+
customZoomScale: z.number().optional(),
|
|
102
|
+
dummyDocument: z.boolean().optional(),
|
|
103
|
+
formatVersion: z.number().int().nonnegative().optional(),
|
|
104
|
+
keyboardMetadata: z
|
|
105
|
+
.object({ count: z.number().int().nonnegative(), timestamp: z.number() })
|
|
106
|
+
.passthrough()
|
|
107
|
+
.optional(),
|
|
108
|
+
lastOpenedPage: z.number().int().optional(),
|
|
109
|
+
margins: z.number().int().nonnegative().optional(),
|
|
110
|
+
originalPageCount: z.number().int().optional(),
|
|
111
|
+
pages: z.array(z.string()).nullable().optional(),
|
|
112
|
+
pageTags: z.array(pageTag).optional(),
|
|
113
|
+
redirectionPageMap: z.array(z.number().int()).optional(),
|
|
114
|
+
sizeInBytes: z.string().optional(),
|
|
115
|
+
transform: z
|
|
116
|
+
.object({
|
|
117
|
+
m11: z.number().optional(),
|
|
118
|
+
m12: z.number().optional(),
|
|
119
|
+
m13: z.number().optional(),
|
|
120
|
+
m21: z.number().optional(),
|
|
121
|
+
m22: z.number().optional(),
|
|
122
|
+
m23: z.number().optional(),
|
|
123
|
+
m31: z.number().optional(),
|
|
124
|
+
m32: z.number().optional(),
|
|
125
|
+
m33: z.number().optional(),
|
|
126
|
+
})
|
|
127
|
+
.passthrough()
|
|
128
|
+
.optional(),
|
|
129
|
+
viewBackgroundFilter: z.enum(["off", "fullpage"]).optional(),
|
|
130
|
+
zoomMode: z
|
|
131
|
+
.enum(["bestFit", "customFit", "fitToHeight", "fitToWidth"])
|
|
132
|
+
.optional(),
|
|
118
133
|
};
|
|
119
|
-
const
|
|
120
|
-
...documentContentOptional
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
}
|
|
134
|
+
const commonDocumentContent = z
|
|
135
|
+
.object({ ...documentContentRequired, ...documentContentOptional })
|
|
136
|
+
.passthrough();
|
|
137
|
+
const documentContent = commonDocumentContent
|
|
138
|
+
.extend({ tags: z.array(tag).optional() })
|
|
139
|
+
.passthrough();
|
|
140
|
+
const legacyDocumentContent = commonDocumentContent
|
|
141
|
+
.extend({ tags: z.array(z.string()).optional() })
|
|
142
|
+
.passthrough();
|
|
143
|
+
const templateContent = z
|
|
144
|
+
.object({
|
|
145
|
+
name: z.string(),
|
|
146
|
+
author: z.string(),
|
|
147
|
+
iconData: z.string(),
|
|
148
|
+
categories: z.array(z.string()),
|
|
149
|
+
labels: z.array(z.string()),
|
|
150
|
+
orientation: z.enum(["portrait", "landscape"]),
|
|
151
|
+
templateVersion: z.string(),
|
|
152
|
+
supportedScreens: z.array(z.enum(["rm2", "rmPP"])),
|
|
153
|
+
constants: z.array(z.record(z.string(), z.number().int())).optional(),
|
|
154
|
+
items: z.array(z.unknown()),
|
|
155
|
+
formatVersion: z.number().int().nonnegative().optional(),
|
|
156
|
+
})
|
|
157
|
+
.strict();
|
|
158
|
+
// content payloads aren't discriminable (legacy/modern differ only by tags
|
|
159
|
+
// element type), so this is an ordered union: the first matching variant wins
|
|
160
|
+
const content = z.union([
|
|
161
|
+
collectionContent,
|
|
162
|
+
legacyCollectionContent,
|
|
163
|
+
templateContent,
|
|
164
|
+
documentContent,
|
|
165
|
+
legacyDocumentContent,
|
|
166
|
+
]);
|
|
167
|
+
const metadata = z
|
|
168
|
+
.object({
|
|
169
|
+
lastModified: z.string(),
|
|
170
|
+
parent: z.string(),
|
|
171
|
+
pinned: z.boolean(),
|
|
172
|
+
type: z.enum(["DocumentType", "CollectionType", "TemplateType"]),
|
|
173
|
+
visibleName: z.string(),
|
|
174
|
+
lastOpened: z.string().optional(),
|
|
175
|
+
lastOpenedPage: z.number().int().optional(),
|
|
176
|
+
createdTime: z.string().optional(),
|
|
177
|
+
deleted: z.boolean().optional(),
|
|
178
|
+
metadatamodified: z.boolean().optional(),
|
|
179
|
+
modified: z.boolean().optional(),
|
|
180
|
+
synced: z.boolean().optional(),
|
|
181
|
+
version: z.number().int().nonnegative().optional(),
|
|
182
|
+
new: z.boolean().optional(),
|
|
183
|
+
source: z.string().optional(),
|
|
184
|
+
})
|
|
185
|
+
.passthrough();
|
|
186
|
+
const updatedRootHash = z
|
|
187
|
+
.object({
|
|
188
|
+
hash: z.string(),
|
|
189
|
+
generation: z.number(),
|
|
190
|
+
})
|
|
191
|
+
.passthrough();
|
|
192
|
+
const rootHash = z
|
|
193
|
+
.object({
|
|
194
|
+
hash: z.string(),
|
|
195
|
+
generation: z.number(),
|
|
196
|
+
schemaVersion: z.number().int().nonnegative(),
|
|
197
|
+
})
|
|
198
|
+
.passthrough();
|
|
199
|
+
const nativeSimpleEntry = z
|
|
200
|
+
.object({
|
|
201
|
+
docID: z.string(),
|
|
202
|
+
hash: z.string(),
|
|
203
|
+
})
|
|
204
|
+
.passthrough();
|
|
170
205
|
async function digest(buff) {
|
|
171
206
|
const digest = await crypto.subtle.digest("SHA-256",
|
|
172
207
|
// NOTE this is type hinted wrong, but it does work correctly on a uint8 view
|
|
@@ -220,9 +255,7 @@ export class RawRemarkable {
|
|
|
220
255
|
const res = await this.#authedFetch("GET", `${this.#rawHost}/sync/v4/root`);
|
|
221
256
|
const raw = await res.text();
|
|
222
257
|
const loaded = JSON.parse(raw);
|
|
223
|
-
|
|
224
|
-
throw Error("invalid root hash");
|
|
225
|
-
const { hash, generation, schemaVersion } = loaded;
|
|
258
|
+
const { hash, generation, schemaVersion } = rootHash.parse(loaded);
|
|
226
259
|
if (schemaVersion !== 3 && schemaVersion !== 4) {
|
|
227
260
|
throw new Error(`schema version ${schemaVersion} not supported`);
|
|
228
261
|
}
|
|
@@ -304,34 +337,12 @@ export class RawRemarkable {
|
|
|
304
337
|
async getContent(fileName, hash) {
|
|
305
338
|
const raw = await this.getText(fileName, hash);
|
|
306
339
|
const loaded = JSON.parse(raw);
|
|
307
|
-
|
|
308
|
-
// defined or not. As a result, we try each, and concatenate the errors at the end
|
|
309
|
-
const errors = [];
|
|
310
|
-
for (const [name, valid] of [
|
|
311
|
-
["collection", collectionContent],
|
|
312
|
-
["legacy collection", legacyCollectionContent],
|
|
313
|
-
["template", templateContent],
|
|
314
|
-
["document", documentContent],
|
|
315
|
-
["legacy document", legacyDocumentContent],
|
|
316
|
-
]) {
|
|
317
|
-
try {
|
|
318
|
-
if (valid.guardAssert(loaded))
|
|
319
|
-
return loaded;
|
|
320
|
-
}
|
|
321
|
-
catch (ex) {
|
|
322
|
-
const msg = ex instanceof Error ? ex.message : "unknown error type";
|
|
323
|
-
errors.push(`Couldn't validate as ${name} because:\n${msg}`);
|
|
324
|
-
}
|
|
325
|
-
}
|
|
326
|
-
const joined = errors.join("\n\nor\n\n");
|
|
327
|
-
throw new Error(`invalid content: ${joined}`);
|
|
340
|
+
return content.parse(loaded);
|
|
328
341
|
}
|
|
329
342
|
async getMetadata(fileName, hash) {
|
|
330
343
|
const raw = await this.getText(fileName, hash);
|
|
331
344
|
const loaded = JSON.parse(raw);
|
|
332
|
-
|
|
333
|
-
throw Error("invalid metadata");
|
|
334
|
-
return loaded;
|
|
345
|
+
return metadata.parse(loaded);
|
|
335
346
|
}
|
|
336
347
|
async putRootHash(hash, generation, broadcast = true) {
|
|
337
348
|
if (!Number.isSafeInteger(generation)) {
|
|
@@ -348,9 +359,7 @@ export class RawRemarkable {
|
|
|
348
359
|
const resp = await this.#authedFetch("PUT", `${this.#rawHost}/sync/v3/root`, { body });
|
|
349
360
|
const raw = await resp.text();
|
|
350
361
|
const loaded = JSON.parse(raw);
|
|
351
|
-
|
|
352
|
-
throw Error("invalid root hash");
|
|
353
|
-
const { hash: newHash, generation: newGen } = loaded;
|
|
362
|
+
const { hash: newHash, generation: newGen } = updatedRootHash.parse(loaded);
|
|
354
363
|
if (Number.isSafeInteger(newGen)) {
|
|
355
364
|
return [newHash, newGen];
|
|
356
365
|
}
|
|
@@ -369,7 +378,6 @@ export class RawRemarkable {
|
|
|
369
378
|
body: bytes,
|
|
370
379
|
headers: {
|
|
371
380
|
"rm-filename": fileName,
|
|
372
|
-
// eslint-disable-next-line spellcheck/spell-checker
|
|
373
381
|
"x-goog-hash": `crc32c=${crcHash}`,
|
|
374
382
|
},
|
|
375
383
|
});
|
|
@@ -425,14 +433,14 @@ export class RawRemarkable {
|
|
|
425
433
|
}
|
|
426
434
|
// NOTE v3 collections have a special hash function, the hash of their
|
|
427
435
|
// contents, so this needs to be different
|
|
428
|
-
entries.sort((a, b) => a.id.localeCompare(b.id));
|
|
429
|
-
const size =
|
|
436
|
+
const sorted = [...entries].sort((a, b) => a.id.localeCompare(b.id));
|
|
437
|
+
const size = sorted.reduce((acc, ent) => acc + ent.size, 0);
|
|
430
438
|
const records = [`${schemaVersion}\n`];
|
|
431
439
|
if (schemaVersion === 4) {
|
|
432
440
|
const name = id === "root" ? "." : id;
|
|
433
|
-
records.push(`0:${name}:${
|
|
441
|
+
records.push(`0:${name}:${sorted.length}:${size}\n`);
|
|
434
442
|
}
|
|
435
|
-
for (const { hash, type, id, subfiles, size } of
|
|
443
|
+
for (const { hash, type, id, subfiles, size } of sorted) {
|
|
436
444
|
const lineType = schemaVersion === 4 ? 0 : type;
|
|
437
445
|
records.push(`${hash}:${lineType}:${id}:${subfiles}:${size}\n`);
|
|
438
446
|
}
|
|
@@ -442,7 +450,7 @@ export class RawRemarkable {
|
|
|
442
450
|
if (schemaVersion === 3) {
|
|
443
451
|
// in schema version 3 an entry's hash is the hash of the concatenated hashes
|
|
444
452
|
const hashBuffs = [];
|
|
445
|
-
for (const { hash } of
|
|
453
|
+
for (const { hash } of sorted) {
|
|
446
454
|
hashBuffs.push(Uint8Array.fromHex(hash));
|
|
447
455
|
}
|
|
448
456
|
hash = await digest(concatArrays(hashBuffs));
|
|
@@ -458,7 +466,7 @@ export class RawRemarkable {
|
|
|
458
466
|
id,
|
|
459
467
|
hash,
|
|
460
468
|
type: schemaVersion > 3 ? 0 : 80000000,
|
|
461
|
-
subfiles:
|
|
469
|
+
subfiles: sorted.length,
|
|
462
470
|
size,
|
|
463
471
|
};
|
|
464
472
|
return [res, this.#putFile(`${id}.docSchema`, hash, entryBuff)];
|
|
@@ -477,9 +485,7 @@ export class RawRemarkable {
|
|
|
477
485
|
},
|
|
478
486
|
});
|
|
479
487
|
const loaded = (await resp.json());
|
|
480
|
-
|
|
481
|
-
throw Error("invalid upload response");
|
|
482
|
-
const { docID, hash } = loaded;
|
|
488
|
+
const { docID, hash } = nativeSimpleEntry.parse(loaded);
|
|
483
489
|
return { id: docID, hash };
|
|
484
490
|
}
|
|
485
491
|
dumpCache() {
|