rmapi-js 14.1.0 → 14.3.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/index.d.ts +71 -0
- package/dist/index.js +192 -22
- package/dist/rmapi-js.esm.min.js +42 -43
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -86,6 +86,18 @@ export declare class ResponseError extends Error {
|
|
|
86
86
|
readonly statusText: string;
|
|
87
87
|
constructor(status: number, statusText: string, message: string);
|
|
88
88
|
}
|
|
89
|
+
/** an error that results from the service rejecting a registration */
|
|
90
|
+
export declare class RegisterError extends ResponseError {
|
|
91
|
+
/** the response body */
|
|
92
|
+
readonly body: string;
|
|
93
|
+
constructor(status: number, statusText: string, body: string);
|
|
94
|
+
}
|
|
95
|
+
/** an error that results from the service refusing to issue a session token */
|
|
96
|
+
export declare class AuthError extends ResponseError {
|
|
97
|
+
/** the response body */
|
|
98
|
+
readonly body: string;
|
|
99
|
+
constructor(status: number, statusText: string, body: string);
|
|
100
|
+
}
|
|
89
101
|
/** options for registering with the api */
|
|
90
102
|
export interface RegisterOptions {
|
|
91
103
|
/**
|
|
@@ -110,6 +122,7 @@ export interface RegisterOptions {
|
|
|
110
122
|
* token to use the api.
|
|
111
123
|
*
|
|
112
124
|
* @param code - the eight letter code a user got from `https://my.remarkable.com/device/browser/connect`.
|
|
125
|
+
* @throws RegisterError if the service rejects the registration
|
|
113
126
|
* @returns the device token necessary for creating an api instace. These never expire so persist as long as necessary.
|
|
114
127
|
*/
|
|
115
128
|
export declare function register(code: string, { deviceDesc, uuid, authHost, }?: RegisterOptions): Promise<string>;
|
|
@@ -667,6 +680,28 @@ declare class Remarkable {
|
|
|
667
680
|
* @returns a reference to the deleted entry, with its new hash
|
|
668
681
|
*/
|
|
669
682
|
delete(ref: ItemRef, refresh?: boolean): Promise<ItemRef>;
|
|
683
|
+
/**
|
|
684
|
+
* permanently delete an entry
|
|
685
|
+
*
|
|
686
|
+
* Unlike {@link delete | `delete`}, which moves an entry to the trash where
|
|
687
|
+
* the device can still restore it, this drops the entry from the account
|
|
688
|
+
* outright. Its files stay in the cloud, but nothing points at them anymore
|
|
689
|
+
* and nothing brings the entry back.
|
|
690
|
+
*
|
|
691
|
+
* Only the entry named goes: purging a folder leaves everything inside it
|
|
692
|
+
* pointing at a parent that's no longer there. Those entries stay in the
|
|
693
|
+
* account and {@link listItems | `listItems`} still returns them, but no
|
|
694
|
+
* folder holds them, so nothing browsing the tree will find them. Purge the
|
|
695
|
+
* contents first, or use {@link purgeTrash | `purgeTrash`}, which takes the
|
|
696
|
+
* whole tree.
|
|
697
|
+
*
|
|
698
|
+
* @example
|
|
699
|
+
* ```ts
|
|
700
|
+
* await api.purge(file);
|
|
701
|
+
* ```
|
|
702
|
+
* @param ref - a reference to the entry to purge
|
|
703
|
+
*/
|
|
704
|
+
purge(ref: ItemRef, refresh?: boolean): Promise<void>;
|
|
670
705
|
/**
|
|
671
706
|
* rename an entry
|
|
672
707
|
*
|
|
@@ -716,6 +751,41 @@ declare class Remarkable {
|
|
|
716
751
|
* @returns references to the deleted entries, each with its new hash
|
|
717
752
|
*/
|
|
718
753
|
bulkDelete(refs: readonly ItemRef[], refresh?: boolean): Promise<ItemRef[]>;
|
|
754
|
+
/**
|
|
755
|
+
* permanently delete many entries
|
|
756
|
+
*
|
|
757
|
+
* The bulk form of {@link purge | `purge`}, done in a single root write.
|
|
758
|
+
*
|
|
759
|
+
* @example
|
|
760
|
+
* ```ts
|
|
761
|
+
* await api.bulkPurge([file]);
|
|
762
|
+
* ```
|
|
763
|
+
*
|
|
764
|
+
* @param refs - references to the entries to purge
|
|
765
|
+
*/
|
|
766
|
+
bulkPurge(refs: readonly ItemRef[], refresh?: boolean): Promise<void>;
|
|
767
|
+
/**
|
|
768
|
+
* permanently delete everything in the trash
|
|
769
|
+
*
|
|
770
|
+
* Trashing a folder doesn't touch what's inside it — those entries keep
|
|
771
|
+
* naming the folder as their parent, which is what lets the device restore
|
|
772
|
+
* them together — so the trash holds the whole tree hanging off it, not just
|
|
773
|
+
* the entries whose parent is "trash". This purges all of it in one root
|
|
774
|
+
* write.
|
|
775
|
+
*
|
|
776
|
+
* @example
|
|
777
|
+
* ```ts
|
|
778
|
+
* await api.purgeTrash();
|
|
779
|
+
* ```
|
|
780
|
+
*
|
|
781
|
+
* @remarks
|
|
782
|
+
* Finding that tree means reading every item's metadata, so this costs about
|
|
783
|
+
* as much as {@link listItems | `listItems`}.
|
|
784
|
+
*
|
|
785
|
+
* @param refresh - if true, refresh the root hash before purging
|
|
786
|
+
* @returns references to the entries that were purged
|
|
787
|
+
*/
|
|
788
|
+
purgeTrash(refresh?: boolean): Promise<ItemRef[]>;
|
|
719
789
|
/**
|
|
720
790
|
* listen for sync notifications
|
|
721
791
|
*
|
|
@@ -855,6 +925,7 @@ export interface RemarkableOptions extends AuthOptions, RemarkableSessionOptions
|
|
|
855
925
|
*
|
|
856
926
|
* @param deviceToken - the device token proving this api instance is
|
|
857
927
|
* registered. Create one with {@link register}.
|
|
928
|
+
* @throws AuthError if the service refuses to issue a session token
|
|
858
929
|
* @returns the session token returned by the reMarkable service
|
|
859
930
|
*/
|
|
860
931
|
export declare function auth(deviceToken: string, { authHost }?: AuthOptions): Promise<string>;
|
package/dist/index.js
CHANGED
|
@@ -205,6 +205,24 @@ export class ResponseError extends Error {
|
|
|
205
205
|
this.statusText = statusText;
|
|
206
206
|
}
|
|
207
207
|
}
|
|
208
|
+
/** an error that results from the service rejecting a registration */
|
|
209
|
+
export class RegisterError extends ResponseError {
|
|
210
|
+
/** the response body */
|
|
211
|
+
body;
|
|
212
|
+
constructor(status, statusText, body) {
|
|
213
|
+
super(status, statusText, "couldn't register api");
|
|
214
|
+
this.body = body;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
/** an error that results from the service refusing to issue a session token */
|
|
218
|
+
export class AuthError extends ResponseError {
|
|
219
|
+
/** the response body */
|
|
220
|
+
body;
|
|
221
|
+
constructor(status, statusText, body) {
|
|
222
|
+
super(status, statusText, "couldn't fetch auth token");
|
|
223
|
+
this.body = body;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
208
226
|
/**
|
|
209
227
|
* register a device and get the token needed to access the api
|
|
210
228
|
*
|
|
@@ -213,6 +231,7 @@ export class ResponseError extends Error {
|
|
|
213
231
|
* token to use the api.
|
|
214
232
|
*
|
|
215
233
|
* @param code - the eight letter code a user got from `https://my.remarkable.com/device/browser/connect`.
|
|
234
|
+
* @throws RegisterError if the service rejects the registration
|
|
216
235
|
* @returns the device token necessary for creating an api instace. These never expire so persist as long as necessary.
|
|
217
236
|
*/
|
|
218
237
|
export async function register(code, { deviceDesc = "browser-chrome", uuid = uuid4(), authHost = AUTH_HOST, } = {}) {
|
|
@@ -223,6 +242,7 @@ export async function register(code, { deviceDesc = "browser-chrome", uuid = uui
|
|
|
223
242
|
method: "POST",
|
|
224
243
|
headers: {
|
|
225
244
|
Authorization: "Bearer",
|
|
245
|
+
"Content-Type": "application/json",
|
|
226
246
|
},
|
|
227
247
|
body: JSON.stringify({
|
|
228
248
|
code,
|
|
@@ -230,11 +250,12 @@ export async function register(code, { deviceDesc = "browser-chrome", uuid = uui
|
|
|
230
250
|
deviceID: uuid,
|
|
231
251
|
}),
|
|
232
252
|
});
|
|
253
|
+
const body = await resp.text();
|
|
233
254
|
if (!resp.ok) {
|
|
234
|
-
throw new
|
|
255
|
+
throw new RegisterError(resp.status, resp.statusText, body);
|
|
235
256
|
}
|
|
236
257
|
else {
|
|
237
|
-
return
|
|
258
|
+
return body;
|
|
238
259
|
}
|
|
239
260
|
}
|
|
240
261
|
const tokenClaims = z
|
|
@@ -407,26 +428,35 @@ class Remarkable {
|
|
|
407
428
|
hash: rootHash,
|
|
408
429
|
});
|
|
409
430
|
entries.push(entry);
|
|
410
|
-
|
|
411
|
-
{
|
|
412
|
-
const env_2 = { stack: [], error: void 0, hasError: false };
|
|
413
|
-
try {
|
|
414
|
-
const rootEntry = __addDisposableResource(env_2, await this.raw.putEntries(ROOT_LIST, entries, 4), true);
|
|
415
|
-
newRoot = rootEntry.hash;
|
|
416
|
-
}
|
|
417
|
-
catch (e_2) {
|
|
418
|
-
env_2.error = e_2;
|
|
419
|
-
env_2.hasError = true;
|
|
420
|
-
}
|
|
421
|
-
finally {
|
|
422
|
-
const result_2 = __disposeResources(env_2);
|
|
423
|
-
if (result_2)
|
|
424
|
-
await result_2;
|
|
425
|
-
}
|
|
426
|
-
}
|
|
427
|
-
await this.#putRootHash(newRoot, generation);
|
|
431
|
+
await this.#writeRoot(entries, generation);
|
|
428
432
|
});
|
|
429
433
|
}
|
|
434
|
+
/**
|
|
435
|
+
* upload a new root index and point the account at it
|
|
436
|
+
*
|
|
437
|
+
* Every blob the index names must already be uploaded, since the root hash
|
|
438
|
+
* lands as soon as the index does.
|
|
439
|
+
*/
|
|
440
|
+
async #writeRoot(entries, generation) {
|
|
441
|
+
let newRoot;
|
|
442
|
+
{
|
|
443
|
+
const env_2 = { stack: [], error: void 0, hasError: false };
|
|
444
|
+
try {
|
|
445
|
+
const rootEntry = __addDisposableResource(env_2, await this.raw.putEntries(ROOT_LIST, entries, 4), true);
|
|
446
|
+
newRoot = rootEntry.hash;
|
|
447
|
+
}
|
|
448
|
+
catch (e_2) {
|
|
449
|
+
env_2.error = e_2;
|
|
450
|
+
env_2.hasError = true;
|
|
451
|
+
}
|
|
452
|
+
finally {
|
|
453
|
+
const result_2 = __disposeResources(env_2);
|
|
454
|
+
if (result_2)
|
|
455
|
+
await result_2;
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
await this.#putRootHash(newRoot, generation);
|
|
459
|
+
}
|
|
430
460
|
async #authedFetch(url, { body, method = "POST", headers = {}, }) {
|
|
431
461
|
// the root PUT is a compare-and-set; retrying a lost-but-applied response
|
|
432
462
|
// would resurface as a false generation conflict and be double-applied by
|
|
@@ -1517,6 +1547,30 @@ class Remarkable {
|
|
|
1517
1547
|
async delete(ref, refresh = false) {
|
|
1518
1548
|
return await this.move(ref, TRASH_ID, refresh);
|
|
1519
1549
|
}
|
|
1550
|
+
/**
|
|
1551
|
+
* permanently delete an entry
|
|
1552
|
+
*
|
|
1553
|
+
* Unlike {@link delete | `delete`}, which moves an entry to the trash where
|
|
1554
|
+
* the device can still restore it, this drops the entry from the account
|
|
1555
|
+
* outright. Its files stay in the cloud, but nothing points at them anymore
|
|
1556
|
+
* and nothing brings the entry back.
|
|
1557
|
+
*
|
|
1558
|
+
* Only the entry named goes: purging a folder leaves everything inside it
|
|
1559
|
+
* pointing at a parent that's no longer there. Those entries stay in the
|
|
1560
|
+
* account and {@link listItems | `listItems`} still returns them, but no
|
|
1561
|
+
* folder holds them, so nothing browsing the tree will find them. Purge the
|
|
1562
|
+
* contents first, or use {@link purgeTrash | `purgeTrash`}, which takes the
|
|
1563
|
+
* whole tree.
|
|
1564
|
+
*
|
|
1565
|
+
* @example
|
|
1566
|
+
* ```ts
|
|
1567
|
+
* await api.purge(file);
|
|
1568
|
+
* ```
|
|
1569
|
+
* @param ref - a reference to the entry to purge
|
|
1570
|
+
*/
|
|
1571
|
+
async purge(ref, refresh = false) {
|
|
1572
|
+
await this.bulkPurge([ref], refresh);
|
|
1573
|
+
}
|
|
1520
1574
|
/**
|
|
1521
1575
|
* rename an entry
|
|
1522
1576
|
*
|
|
@@ -1631,6 +1685,118 @@ class Remarkable {
|
|
|
1631
1685
|
async bulkDelete(refs, refresh = false) {
|
|
1632
1686
|
return await this.bulkMove(refs, TRASH_ID, refresh);
|
|
1633
1687
|
}
|
|
1688
|
+
/**
|
|
1689
|
+
* permanently delete many entries
|
|
1690
|
+
*
|
|
1691
|
+
* The bulk form of {@link purge | `purge`}, done in a single root write.
|
|
1692
|
+
*
|
|
1693
|
+
* @example
|
|
1694
|
+
* ```ts
|
|
1695
|
+
* await api.bulkPurge([file]);
|
|
1696
|
+
* ```
|
|
1697
|
+
*
|
|
1698
|
+
* @param refs - references to the entries to purge
|
|
1699
|
+
*/
|
|
1700
|
+
async bulkPurge(refs, refresh = false) {
|
|
1701
|
+
if (!refs.length) {
|
|
1702
|
+
return;
|
|
1703
|
+
}
|
|
1704
|
+
await this.#withRetry(async () => {
|
|
1705
|
+
const [rootHash, generation] = await this.#getRootHash(refresh);
|
|
1706
|
+
const { entries } = await this.raw.getEntries({
|
|
1707
|
+
id: ROOT_LIST,
|
|
1708
|
+
hash: rootHash,
|
|
1709
|
+
});
|
|
1710
|
+
const wanted = new Set(refs.map((ref) => `${ref.id}\0${ref.hash}`));
|
|
1711
|
+
const found = new Set();
|
|
1712
|
+
const newEntries = [];
|
|
1713
|
+
for (const entry of entries) {
|
|
1714
|
+
const key = `${entry.id}\0${entry.hash}`;
|
|
1715
|
+
if (wanted.has(key)) {
|
|
1716
|
+
found.add(key);
|
|
1717
|
+
}
|
|
1718
|
+
else {
|
|
1719
|
+
newEntries.push(entry);
|
|
1720
|
+
}
|
|
1721
|
+
}
|
|
1722
|
+
for (const ref of refs) {
|
|
1723
|
+
if (!found.has(`${ref.id}\0${ref.hash}`)) {
|
|
1724
|
+
throw new HashNotFoundError(ref.hash);
|
|
1725
|
+
}
|
|
1726
|
+
}
|
|
1727
|
+
await this.#writeRoot(newEntries, generation);
|
|
1728
|
+
});
|
|
1729
|
+
}
|
|
1730
|
+
/**
|
|
1731
|
+
* permanently delete everything in the trash
|
|
1732
|
+
*
|
|
1733
|
+
* Trashing a folder doesn't touch what's inside it — those entries keep
|
|
1734
|
+
* naming the folder as their parent, which is what lets the device restore
|
|
1735
|
+
* them together — so the trash holds the whole tree hanging off it, not just
|
|
1736
|
+
* the entries whose parent is "trash". This purges all of it in one root
|
|
1737
|
+
* write.
|
|
1738
|
+
*
|
|
1739
|
+
* @example
|
|
1740
|
+
* ```ts
|
|
1741
|
+
* await api.purgeTrash();
|
|
1742
|
+
* ```
|
|
1743
|
+
*
|
|
1744
|
+
* @remarks
|
|
1745
|
+
* Finding that tree means reading every item's metadata, so this costs about
|
|
1746
|
+
* as much as {@link listItems | `listItems`}.
|
|
1747
|
+
*
|
|
1748
|
+
* @param refresh - if true, refresh the root hash before purging
|
|
1749
|
+
* @returns references to the entries that were purged
|
|
1750
|
+
*/
|
|
1751
|
+
async purgeTrash(refresh = false) {
|
|
1752
|
+
return await this.#withRetry(async () => {
|
|
1753
|
+
const [rootHash, generation] = await this.#getRootHash(refresh);
|
|
1754
|
+
const { entries } = await this.raw.getEntries({
|
|
1755
|
+
id: ROOT_LIST,
|
|
1756
|
+
hash: rootHash,
|
|
1757
|
+
});
|
|
1758
|
+
const parents = await Promise.all(entries.map(async (entry) => (await this.getMetadata(entry)).parent));
|
|
1759
|
+
const children = new Map();
|
|
1760
|
+
for (const [ind, entry] of entries.entries()) {
|
|
1761
|
+
const siblings = children.get(parents[ind]);
|
|
1762
|
+
if (siblings === undefined) {
|
|
1763
|
+
children.set(parents[ind], [entry]);
|
|
1764
|
+
}
|
|
1765
|
+
else {
|
|
1766
|
+
siblings.push(entry);
|
|
1767
|
+
}
|
|
1768
|
+
}
|
|
1769
|
+
const trashed = new Set();
|
|
1770
|
+
let frontier = children.get(TRASH_ID) ?? [];
|
|
1771
|
+
while (frontier.length) {
|
|
1772
|
+
const next = [];
|
|
1773
|
+
for (const { id } of frontier) {
|
|
1774
|
+
// skipping what we've already seen also ends a parent cycle, which
|
|
1775
|
+
// the account shouldn't have but which would loop here forever
|
|
1776
|
+
if (!trashed.has(id)) {
|
|
1777
|
+
trashed.add(id);
|
|
1778
|
+
next.push(...(children.get(id) ?? []));
|
|
1779
|
+
}
|
|
1780
|
+
}
|
|
1781
|
+
frontier = next;
|
|
1782
|
+
}
|
|
1783
|
+
if (!trashed.size) {
|
|
1784
|
+
return [];
|
|
1785
|
+
}
|
|
1786
|
+
const purged = [];
|
|
1787
|
+
const newEntries = [];
|
|
1788
|
+
for (const entry of entries) {
|
|
1789
|
+
if (trashed.has(entry.id)) {
|
|
1790
|
+
purged.push({ id: entry.id, hash: entry.hash });
|
|
1791
|
+
}
|
|
1792
|
+
else {
|
|
1793
|
+
newEntries.push(entry);
|
|
1794
|
+
}
|
|
1795
|
+
}
|
|
1796
|
+
await this.#writeRoot(newEntries, generation);
|
|
1797
|
+
return purged;
|
|
1798
|
+
});
|
|
1799
|
+
}
|
|
1634
1800
|
/**
|
|
1635
1801
|
* listen for sync notifications
|
|
1636
1802
|
*
|
|
@@ -1840,6 +2006,7 @@ function decodeCache(dumped) {
|
|
|
1840
2006
|
*
|
|
1841
2007
|
* @param deviceToken - the device token proving this api instance is
|
|
1842
2008
|
* registered. Create one with {@link register}.
|
|
2009
|
+
* @throws AuthError if the service refuses to issue a session token
|
|
1843
2010
|
* @returns the session token returned by the reMarkable service
|
|
1844
2011
|
*/
|
|
1845
2012
|
export async function auth(deviceToken, { authHost = AUTH_HOST } = {}) {
|
|
@@ -1849,10 +2016,13 @@ export async function auth(deviceToken, { authHost = AUTH_HOST } = {}) {
|
|
|
1849
2016
|
Authorization: `Bearer ${deviceToken}`,
|
|
1850
2017
|
},
|
|
1851
2018
|
});
|
|
2019
|
+
const body = await resp.text();
|
|
1852
2020
|
if (!resp.ok) {
|
|
1853
|
-
throw new
|
|
2021
|
+
throw new AuthError(resp.status, resp.statusText, body);
|
|
2022
|
+
}
|
|
2023
|
+
else {
|
|
2024
|
+
return body;
|
|
1854
2025
|
}
|
|
1855
|
-
return await resp.text();
|
|
1856
2026
|
}
|
|
1857
2027
|
/**
|
|
1858
2028
|
* Create an API instance from an existing session token.
|