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