Skip to content

Commit dd2b7ec

Browse files
authored
add upload api (#27)
1 parent 0010cf8 commit dd2b7ec

File tree

4 files changed

+137
-72
lines changed

4 files changed

+137
-72
lines changed

src/index.spec.ts

Lines changed: 8 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -389,29 +389,16 @@ ${epubHash}:0:doc.epub:0:1
389389
const pdf = enc.encode("pdf content");
390390
mockFetch(
391391
emptyResponse(),
392-
jsonResponse({
393-
hash: repHash("abcd0123"),
394-
generation: 0,
395-
schemaVersion: 3,
396-
}),
397-
emptyResponse(), // .content
398-
emptyResponse(), // .metadata
399-
// eslint-disable-next-line spellcheck/spell-checker
400-
emptyResponse(), // .pagedata
401-
emptyResponse(), // .pdf
402-
textResponse("3\n"),
403-
emptyResponse(), // .docSchema
404-
emptyResponse(), // root.docSchema
405392
jsonResponse({
406393
hash: repHash("1"),
407-
generation: 1,
408-
}), // root hash
394+
docID: "fake pdf id",
395+
}),
409396
);
410397

411398
const api = await remarkable("");
412399
const res = await api.uploadPdf("new name", pdf);
413400

414-
expect(res.id).toHaveLength(36);
401+
expect(res.id).toBe("fake pdf id");
415402
expect(res.hash).toHaveLength(64);
416403
});
417404

@@ -451,29 +438,16 @@ ${epubHash}:0:doc.epub:0:1
451438
const epub = enc.encode("epub content");
452439
mockFetch(
453440
emptyResponse(),
454-
jsonResponse({
455-
hash: repHash("abcd0123"),
456-
generation: 0,
457-
schemaVersion: 3,
458-
}),
459-
emptyResponse(), // .content
460-
emptyResponse(), // .metadata
461-
// eslint-disable-next-line spellcheck/spell-checker
462-
emptyResponse(), // .pagedata
463-
emptyResponse(), // .epub
464-
textResponse("3\n"),
465-
emptyResponse(), // .docSchema
466-
emptyResponse(), // root.docSchema
467441
jsonResponse({
468442
hash: repHash("1"),
469-
generation: 1,
470-
}), // root hash
443+
docID: "fake epub id",
444+
}),
471445
);
472446

473447
const api = await remarkable("");
474448
const res = await api.uploadEpub("new name", epub);
475449

476-
expect(res.id).toHaveLength(36);
450+
expect(res.id).toBe("fake epub id");
477451
expect(res.hash).toHaveLength(64);
478452
});
479453

@@ -528,7 +502,7 @@ ${epubHash}:0:doc.epub:0:1
528502
);
529503

530504
const api = await remarkable("");
531-
const res = await api.createFolder("new folder");
505+
const res = await api.putFolder("new folder");
532506

533507
expect(res.id).toHaveLength(36);
534508
expect(res.hash).toHaveLength(64);
@@ -824,7 +798,7 @@ hash2:80000000:other:0:2
824798
mockFetch(emptyResponse());
825799

826800
const api = await remarkable("");
827-
expect(api.createFolder("test", { parent: "invalid" })).rejects.toThrow(
801+
expect(api.putFolder("test", { parent: "invalid" })).rejects.toThrow(
828802
"parent must be a valid document id",
829803
);
830804
});

src/index.ts

Lines changed: 47 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ import {
6868
type RawListEntry,
6969
type RawRemarkableApi,
7070
type RequestMethod,
71+
type SimpleEntry,
7172
type Tag,
7273
type TemplateContent,
7374
type TextAlignment,
@@ -94,14 +95,17 @@ export type {
9495
RawFileEntry,
9596
RawListEntry,
9697
RawRemarkableApi,
98+
SimpleEntry,
9799
Tag,
98100
TemplateContent,
99101
TextAlignment,
102+
UploadMimeType,
100103
ZoomMode,
101104
} from "./raw";
102105

103106
const AUTH_HOST = "https://webapp-prod.cloud.remarkable.engineering";
104107
const RAW_HOST = "https://eu.tectonic.remarkable.com";
108+
const UPLOAD_HOST = "https://internal.cloud.remarkable.com";
105109

106110
// ------------ //
107111
// Request Info //
@@ -165,14 +169,6 @@ export interface TemplateType extends EntryCommon {
165169
/** a remarkable entry for cloud items */
166170
export type Entry = CollectionEntry | DocumentType | TemplateType;
167171

168-
/** an simple entry without any extra information */
169-
export interface SimpleEntry {
170-
/** the document id */
171-
id: string;
172-
/** the document hash */
173-
hash: string;
174-
}
175-
176172
/** the new hash of a modified entry */
177173
export interface HashEntry {
178174
/** the actual hash */
@@ -185,6 +181,12 @@ export interface HashesEntry {
185181
hashes: Record<string, string>;
186182
}
187183

184+
/** options for creating a folder */
185+
export interface FolderOptions {
186+
/** the id of the folder's parent directory, "" or omitted for root */
187+
parent?: string;
188+
}
189+
188190
/** An error that gets thrown when the backend while trying to update
189191
*
190192
* IF you encounter this error, you likely just need to try th request again. If
@@ -285,12 +287,6 @@ export async function register(
285287
}
286288
}
287289

288-
/** options available when uploading a document */
289-
export interface UploadOptions {
290-
/** an optional parent id to set when uploading */
291-
parent?: string;
292-
}
293-
294290
/**
295291
* options for putting a file onto reMarkable
296292
*
@@ -513,9 +509,9 @@ export interface RemarkableApi {
513509
): Promise<SimpleEntry>;
514510

515511
/** create a folder */
516-
createFolder(
512+
putFolder(
517513
visibleName: string,
518-
opts?: UploadOptions,
514+
opts?: FolderOptions,
519515
refresh?: boolean,
520516
): Promise<SimpleEntry>;
521517

@@ -528,16 +524,12 @@ export interface RemarkableApi {
528524
* ```
529525
*
530526
* @remarks
531-
* this is now simply a less powerful version of {@link putEpub | `putEpub`}.
527+
* this uses a simpler api that works even with schema version 4.
532528
*
533529
* @param visibleName - the name to show for the uploaded epub
534530
* @param buffer - the epub contents
535531
*/
536-
uploadEpub(
537-
visibleName: string,
538-
buffer: Uint8Array,
539-
opts?: UploadOptions,
540-
): Promise<SimpleEntry>;
532+
uploadEpub(visibleName: string, buffer: Uint8Array): Promise<SimpleEntry>;
541533

542534
/**
543535
* upload a pdf
@@ -548,16 +540,15 @@ export interface RemarkableApi {
548540
* ```
549541
*
550542
* @remarks
551-
* this is now simply a less powerful version of {@link putPdf | `putPdf`}.
543+
* this uses a simpler api that works even with schema version 4.
552544
*
553545
* @param visibleName - the name to show for the uploaded epub
554546
* @param buffer - the epub contents
555547
*/
556-
uploadPdf(
557-
visibleName: string,
558-
buffer: Uint8Array,
559-
opts?: UploadOptions,
560-
): Promise<SimpleEntry>;
548+
uploadPdf(visibleName: string, buffer: Uint8Array): Promise<SimpleEntry>;
549+
550+
/** create a folder using the simple api */
551+
uploadFolder(visibleName: string): Promise<SimpleEntry>;
561552

562553
/**
563554
* update content metadata for a document
@@ -739,6 +730,7 @@ class Remarkable implements RemarkableApi {
739730
constructor(
740731
userToken: string,
741732
rawHost: string,
733+
uploadHost: string,
742734
cache: Map<string, string | null>,
743735
) {
744736
this.#userToken = userToken;
@@ -748,6 +740,7 @@ class Remarkable implements RemarkableApi {
748740
this.#authedFetch(url, { method, body, headers }),
749741
cache,
750742
rawHost,
743+
uploadHost,
751744
);
752745
}
753746

@@ -815,8 +808,6 @@ class Remarkable implements RemarkableApi {
815808
const contentEnt = entries.find((ent) => ent.id.endsWith(".content"));
816809
if (metaEnt === undefined) {
817810
throw new Error(`couldn't find metadata for hash ${hash}`);
818-
} else if (contentEnt === undefined) {
819-
throw new Error(`couldn't find content for hash ${hash}`);
820811
}
821812

822813
const [
@@ -832,7 +823,10 @@ class Remarkable implements RemarkableApi {
832823
content,
833824
] = await Promise.all([
834825
this.raw.getMetadata(metaEnt.hash),
835-
this.raw.getContent(contentEnt.hash),
826+
// collections don't always have content, since content only lists tags
827+
contentEnt === undefined
828+
? Promise.resolve({ fileType: undefined, tags: undefined })
829+
: this.raw.getContent(contentEnt.hash),
836830
]);
837831
if ("templateVersion" in content) {
838832
return {
@@ -1074,9 +1068,9 @@ class Remarkable implements RemarkableApi {
10741068
}
10751069

10761070
/** create a folder */
1077-
async createFolder(
1071+
async putFolder(
10781072
visibleName: string,
1079-
{ parent = "" }: UploadOptions = {},
1073+
{ parent = "" }: FolderOptions = {},
10801074
refresh: boolean = false,
10811075
): Promise<SimpleEntry> {
10821076
if (parent && !idReg.test(parent)) {
@@ -1142,18 +1136,25 @@ class Remarkable implements RemarkableApi {
11421136
async uploadEpub(
11431137
visibleName: string,
11441138
buffer: Uint8Array,
1145-
opts: UploadOptions = {},
11461139
): Promise<SimpleEntry> {
1147-
return await this.putEpub(visibleName, buffer, opts);
1140+
return await this.raw.uploadFile(
1141+
visibleName,
1142+
buffer,
1143+
"application/epub+zip",
1144+
);
11481145
}
11491146

11501147
/** upload a pdf */
11511148
async uploadPdf(
11521149
visibleName: string,
11531150
buffer: Uint8Array,
1154-
opts: UploadOptions = {},
11551151
): Promise<SimpleEntry> {
1156-
return await this.putPdf(visibleName, buffer, opts);
1152+
return await this.raw.uploadFile(visibleName, buffer, "application/pdf");
1153+
}
1154+
1155+
/** upload a folder */
1156+
async uploadFolder(visibleName: string): Promise<SimpleEntry> {
1157+
return await this.raw.uploadFile(visibleName, new Uint8Array(0), "folder");
11571158
}
11581159

11591160
/** edit just a content entry */
@@ -1437,6 +1438,13 @@ export interface RemarkableOptions {
14371438
*/
14381439
syncHost?: string;
14391440

1441+
/**
1442+
* the base url for making upload requests
1443+
*
1444+
* @defaultValue "https://internal.cloud.remarkable.com"
1445+
*/
1446+
uploadHost?: string;
1447+
14401448
/**
14411449
* the url for making requests using the low-level api
14421450
*
@@ -1484,6 +1492,7 @@ export async function remarkable(
14841492
{
14851493
authHost = AUTH_HOST,
14861494
rawHost = RAW_HOST,
1495+
uploadHost = UPLOAD_HOST,
14871496
cache,
14881497
maxCacheSize = Infinity,
14891498
}: RemarkableOptions = {},
@@ -1505,7 +1514,7 @@ export async function remarkable(
15051514
maxCacheSize === Infinity
15061515
? new Map(entries)
15071516
: new LruCache(maxCacheSize, entries);
1508-
return new Remarkable(userToken, rawHost, cache);
1517+
return new Remarkable(userToken, rawHost, uploadHost, cache);
15091518
} else {
15101519
throw new Error(
15111520
"cache was not a valid cache (json string mapping); your cache must be corrupted somehow. Either initialize remarkable without a cache, or fix its format.",

0 commit comments

Comments
 (0)