a-remarkable-js-sdk
Version:
a reMarkable Cloud API wrapper written in TypeScript
546 lines (527 loc) • 22.2 kB
text/typescript
type DeviceDescription = 'browser-chrome' | 'desktop-macos' | 'desktop-windows' | 'mobile-android' | 'mobile-ios' | 'remarkable';
/**
* Represents a reMarkable Cloud API session.
*
* The reMarkable Cloud API uses JWT token based authentication. All requests
* performed to the API (expect the authentication ones) must contain a valid
* JWT token which identifies the (@link Device) performing the request.
*
* The `Session` class provides an interface to access the authentication
* token information and verify its validity.
*/
declare class Session {
readonly deviceId: string;
readonly expiresAt: Date;
readonly token: string;
constructor(sessionToken: string);
get expired(): boolean;
}
/**
* Represents a reMarkable device. Provides an interface to
* authenticate with the reMarkable Cloud API.
*
* The reMarkable Cloud API uses `devices` to authenticate users. A
* `device` is any software paired to a reMarkable Cloud user account.
* `devices` are represented by a `device token`. A `device token` is
* a JWT token without expiration date. It is used to fetch `session tokens`
* from the reMarkable Cloud API, which can be used to perform authenticated
* request to the different API services.
*
* The `Device` class encapsulates the logic to pair new applications to
* reMarkable Cloud user accounts and create user sessions for interacting
* with the reMarkable Cloud API.
*
* Paired `Devices` are listed in: [https://my.remarkable.com/device/remarkable](https://my.remarkable.com/device/remarkable)
*/
declare class Device {
/**
* Creates a new device and pairs it to a reMarkable Cloud user account.
*
* @param id - Unique `device` ID (uuid v4)
* @param description - Label which indicates the `device` running environment (web browser, mobile app, ...)
* @param oneTimeCode - One-time password to authenticate reMarkable Cloud user account when pairing `device`
* @param HttpClientConstructor - HttpClient used for generating pair token
*/
static pair(id: string, description: DeviceDescription, oneTimeCode: string, HttpClientConstructor?: unknown): Promise<Device>;
/**
* `device` unique identifier (uuid v4)
*/
readonly id: string;
/**
* `device` running environment (web browser, mobile app, ...)
*/
readonly description: DeviceDescription;
/**
* reMarkable Cloud API token associated to `device`
*/
readonly token: string;
private readonly httpClient;
constructor(deviceToken: string, HttpClientConstructor?: unknown);
/**
* Creates a new `device` `session`.
*
* Fetches a new session token from the reMarkable Cloud API. This token
* can be used to perform authenticated requests to the reMarkable Cloud API
* in behalf of the user account associated to the `device`.
*/
connect(): Promise<Session>;
}
/**
* Represents a reMarkable Cloud folder
*/
declare class Folder {
id: string;
hash: string;
name: string;
folders?: Folder[];
documents?: Document[];
parentFolder?: Folder;
lastModified?: Date;
constructor(id: string, hash: string, name: string, lastModified: Date, parentFolder?: Folder, folders?: Folder[], documents?: Document[]);
get root(): boolean;
}
/**
* Represents a reMarkable Cloud document
*/
declare class Document {
id: string;
hash: string;
name: string;
fileType: 'pdf' | 'epub' | 'notebook';
folder?: Folder;
lastModified: Date;
lastOpened: Date;
constructor(id: string, hash: string, name: string, fileType: 'pdf' | 'epub' | 'notebook', lastModified?: Date, lastOpened?: Date, folder?: Folder);
}
/**
* The reMarkable API only works with `.pdf` and `.epub` files. This error is
* raised when an `Buffer` with an unsupported file extension is passed to
* the `FileBufferType` class.
*/
declare class UnsupportedFileExtensionError extends Error {
}
/**
* Represents the type of the file associated to its buffer.
*
* It is possible to identify the type of a file by examining its buffer.
* This class encapsulates the logic to infer a files type from its
* respective buffer.
*
* Since the reMarkable Cloud API only allows uploading `.pdf` and `.epub`
* files, the class only handles buffers with that extension, raising an
* { @link UnsupportedFileExtensionError } when a buffer from an unsupported
* file extension is passed.
*/
declare class FileBufferType {
static extension(buffer: Buffer): 'pdf' | 'epub';
static mimeType(buffer: Buffer): string;
/**
* Buffer file extension
*/
readonly extension: string;
/**
* MIME type of the buffer file
*/
readonly mimeType: string;
constructor(buffer: Buffer);
}
type HeadersPayload = Record<string, string>;
/**
* { @link HttpClient } { @link Request } headers
*
* Encapsulates a collection of `http` `header` key - value pairs
* and provides logic to export them in different formats
* compatible with `http` libraries.
*/
declare class Headers {
#private;
constructor(headers: HeadersPayload);
get entries(): Record<string, string>;
}
/**
* { @link HttpClient } { @link Request } context
*
* `HttpClient` instance configuration. Given an `HttpClient` with a
* `Context`, the `Context` represents the base URL and headers set
* used in all http requests performed with the client.
*/
declare class Context {
readonly host: string;
readonly headers: Headers;
constructor(host: string, headers?: HeadersPayload);
}
/**
* HTTP request `body` types supported by { @link HttpClient } { @link Body }.
*
* These types can be converted into a `serialized` format, compatible with `http` libraries,
* by the { @link HttpClient } { @link Body } to be dispatched as `body` in an
* HTTP POST / PATCH / PUT request.
*/
type BodyPayload = Record<string, string | boolean | number> | ArrayBuffer | Buffer | string;
/**
* Interface for library HTTP client.
*
* Provides a RESTful interface for performing HTTP requests. All http
* requests performed by the library should be done through this interface.
*
* `HttpClients` interface consists on a set of static methods for performing
* HTTP requests. To perform multiple requests with a base `host` and set of
* `headers` use the instance methods by instantiating the client with the
* base `host` and `headers` as context.
*/
declare abstract class HttpClient {
static get(host: string, path: string, headers?: HeadersPayload): Promise<Response>;
static post(host: string, path: string, headers?: HeadersPayload, body?: BodyPayload): Promise<Response>;
static patch(host: string, path: string, headers?: HeadersPayload, body?: BodyPayload): Promise<Response>;
static put(host: string, path: string, headers?: HeadersPayload, body?: BodyPayload): Promise<Response>;
static delete(host: string, path: string, headers?: HeadersPayload): Promise<Response>;
readonly context: Context;
constructor(host: string, headers?: HeadersPayload);
/**
* Perform a GET request.
*
* @param path - Request path destination (https://{@link host}/{@link path})
* @param headers - Request headers. Merged with client { @link context.headers } when performing request
*/
get(path: string, headers?: HeadersPayload): Promise<Response>;
/**
* Perform a POST request.
*
* @param path - Request path destination (https://{@link host}/{@link path})
* @param body - Request body payload
* @param headers - Request headers. Merged with client { @link context.headers } when performing request
*/
post(path: string, body?: BodyPayload, headers?: HeadersPayload): Promise<Response>;
/**
* Perform a PATCH request.
*
* @param path - Request path destination (https://{@link host}/{@link path})
* @param body - Request body payload
* @param headers - Request headers. Merged with client { @link context.headers } when performing request
*/
patch(path: string, body?: BodyPayload, headers?: HeadersPayload): Promise<Response>;
/**
* Perform a PUT request.
*
* @param path - Request path destination (https://{@link host}/{@link path})
* @param body - Request body payload
* @param headers - Request headers. Merged with client { @link context.headers } when performing request
*/
put(path: string, body?: BodyPayload, headers?: HeadersPayload): Promise<Response>;
/**
* Perform a DELETE request.
*
* @param path - Request path destination (https://{@link host}/{@link path})
* @param headers - Request headers. Merged with client { @link context.headers } when performing request
*/
delete(path: string, headers?: HeadersPayload): Promise<Response>;
/**
* Get the underlying `HttpClient` instance class reference.
*
* Used to perform HTTP requests through the client specific http static methods.
*
* @private
*/
private classReference;
/**
* Request specific configuration. Merges client { @link context.headers }
* with request specific headers.
*
* @param headers - Request specific headers
*
* @private
*/
private requestContext;
}
/**
* Generates `HttpClient` instances preconfigured for performing
* requests to the different services available in the reMarkable
* Cloud API.
*
* Each service in the reMarkable Cloud API (such as authentication,
* document storage, ...) has a specific host. The `/service` API
* endpoint allows developers to fetch the these hosts. This class
* provides a public interface of methods representing all the
* services available in the API.
*
* Use these methods to get instances of the `HttpClient` configured
* with the corresponding host and authentication headers for the
* service you want to make use of.
*/
declare class ServiceManager {
/**
* Returns `HttpClient` configured with host and header options to
* perform requests to the reMarkable production API.
*
* This API endpoint is not a service per-se, but provides some similar
* utilities other services provide (such as device pairing or session
* creation). To keep the logic to fetch endpoints consistent, this
* method encapsulates the endpoint logic as if it was another service.
*
* Some of the utilities the endpoint provide does not require from
* any headers. By defining this method as static, we can make use
* of this endpoint without requiring a `Device` instance (as it
* is required for other endpoints).
*/
static productionHttpClient(headers?: HeadersPayload, HttpClientConstructor?: unknown): HttpClient;
readonly session: Session;
readonly httpClient: HttpClient;
constructor(session: Session, HttpClientConstructor?: unknown);
/**
* Returns `HttpClient` configured with host and header options to
* perform requests to the reMarkable Document Storage API service.
*/
documentStorageHttpClient(): Promise<HttpClient>;
/**
* Returns `HttpClient` configured with host and header options to
* perform requests to the reMarkable internal API.
*
* This API endpoint is not a service per-se, but provides some similar
* utilities other services provide (such as file upload). To keep the
* logic to fetch endpoints consistent, this method encapsulates the
* endpoint logic as if it was another service.
*/
internalCloudHttpClient(): Promise<HttpClient>;
private serviceHttpClient;
}
declare class FileNotUploadedError extends Error {
}
/**
* Represents a reference to an uploaded { @link Document }
*
* In the reMarkable Cloud API, documents are identified by a unique
* ID and Hash. While the ID is always the same, the hash changes
* conforming changes are pushed to the user account file system.
*
* The { @link DocumentReference } class parses the reMarkable Cloud
* API success upload response and returns a reference to the uploaded
* document.
*/
declare class DocumentReference {
readonly id: string;
readonly hash: string;
constructor(id: string, hash: string);
}
/**
* Represents a file content, encoded as a buffer, ready to be uploaded
* to reMarkable Cloud.
*
* Encapsulates the logic to handle file upload. Given the Buffer
* content of a file, it verifies the type compatibility with the
* reMarkable Cloud API and provides an interface to upload the file
* and get a reference to its cloud equivalent when successfully
* pushed.
*/
declare class FileBuffer {
/**
* Creates a FileBuffer from a local file
*
* Used to easily upload local files to reMarkable cloud
*
* @param {string} path
* @param {ServiceManager} serviceManager
*/
static fromLocalFile(path: string, serviceManager: ServiceManager): Promise<FileBuffer>;
/**
* Creates FileBuffer from file Buffer and uploads it
*
* @param name - File name
* @param buffer - File content
* @param serviceManager
*/
static upload(name: string, buffer: Buffer, serviceManager: ServiceManager): Promise<FileBuffer>;
readonly name: string;
readonly buffer: Buffer;
readonly type: FileBufferType;
documentReference?: DocumentReference;
private httpClient?;
private readonly serviceManager;
constructor(name: string, buffer: Buffer, serviceManager: ServiceManager);
get uploaded(): boolean;
upload(): Promise<DocumentReference>;
private internalCloudHttpClient;
private get encodedName();
}
/**
* Represents a snapshot of the reMarkable Cloud API file system.
*
* Provides a list of all { @link Document }s and { @link Folder }s in
* the user's reMarkable Cloud account in a specific point in time.
*
* A snapshot is valid as long as the user does not perform any write
* operations on the reMarkable Cloud file system. As soon a file in
* the system is uploaded or modified, all the files hashes are modify,
* causing the snapshot to be de-synchornized with the actual reMarkable
* Cloud file system.
*
* You can see the snapshot as a cache of the reMarkable Cloud file system.
*/
declare class FileSystemSnapshot {
#private;
constructor(documents: Document[], folders: Folder[]);
get documents(): Document[];
get folders(): Folder[];
get rootFolder(): Folder;
document(id: string): Document | undefined;
folder(id: string): Folder | undefined;
}
/**
* Represents the reMarkable Cloud API file system. Provides an
* interface to retrieve the list of {@link Document}s and {@link Folder}s
* in a user reMarkable Cloud account and navigate through them.
*
* The reMarkable API `/doc/v2/files` endpoint returns a list of all files
* in a user reMarkable Cloud account with their respective metadata.
*
* The `FileSystem` class parses the API response and maps the file system
* hierarchy to a set of {@link Document} and {@link Folder} instances,
* providing a virtual recreation of the actual file system tree, which
* can be then used to navigate through it in the similar way as in any
* other file system.
*/
declare class FileSystem {
#private;
constructor(serviceManager: ServiceManager);
get lastFetchSnapshot(): FileSystemSnapshot | undefined;
/**
* Requests new reMarkable Cloud snapshot of all documents and folders in user account.
*/
snapshot(): Promise<FileSystemSnapshot>;
/**
* Requests new reMarkable Cloud list of all documents in user account.
*
* Consider using {@link snapshot} method to get a snapshot of the entire file system
* and fetch the documents from there instead of calling this method. Each document
* request triggers a fetch call of the whole file system and the post-processing
* of the response to build the file system tree. This is an expensive operation
* that should be only invoked when changes have been made to the file system
* (files uploaded, updated, deleted, etc).
*
* If no changes were uploaded to the reMarkable Cloud you should use the {@link snapshot}
* method to get the list of documents and folders in the user account. The snapshot works
* as a cache, and contains the file system information of the last request made to the
* reMarkable Cloud API.
*/
document(id: string): Promise<Document | undefined>;
/**
* Requests new reMarkable Cloud list of all folder in user account.
*
* Consider using {@link snapshot} method to get a snapshot of the entire file system
* and fetch the folder from there instead of calling this method. Each folder
* request triggers a fetch call of the whole file system and the post-processing
* of the response to build the file system tree. This is an expensive operation
* that should be only invoked when changes have been made to the file system
* (files uploaded, updated, deleted, etc).
*
* If no changes were uploaded to the reMarkable Cloud you should use the {@link snapshot}
* method to get the list of documents and folders in the user account. The snapshot works
* as a cache, and contains the file system information of the last request made to the
* reMarkable Cloud API.
*/
folder(id: string): Promise<Folder | undefined>;
private fetchSnapshot;
}
/**
* reMarkable API `/downloads` endpoint response payload
*
* Represents the URL to access the content of a `hash`.
*
* Download URLs are signed. The `hash` content is accessible
* through an HTTP request to its `url` with the `method` specified
* (no authentication required).
*
* Once the `expires` date is reached, the URL is no longer valid.
*/
interface HashPathPayload {
expires: string;
method: 'GET' | 'PUT' | 'PATCH';
relative_path: string;
url: string;
}
/**
* Represents the URL to access the content of a `hash`.
*
* A `hash` is a string which uniquely identifies a piece of information in the
* reMarkable cloud. A piece of information can be a `Document`, a `Folder`,
* metadata associated to a `Document` or a `Folder`, etc.
*
* To access the content of a `hash`, the reMarkable API provides a `/downloads`
* endpoint. This endpoint returns the download URL for a given `hash`. Download
* URLs are signed URLs with an expiration date. When performing an HTTP request
* to the download URL, the content of the `hash` is returned. Once the expiration
* date is reached, the URL is no longer valid, requiring a new request to the
* `/downloads` endpoint to get a new download URL.
*
* The `HashUrl` class provides an interface to download the content of a `hash`.
*/
declare class HashUrl {
/**
* Returns `HashUrl` for a given `hash`.
*
* @param {string} hash
* @param {ServiceManager} serviceManager
*/
static fromHash(hash: string, serviceManager: ServiceManager): Promise<HashUrl>;
/**
* Returns `HashUrl` for the `hash` associated to the root folder.
*
* The root folder represents the root path of the reMarkable cloud storage.
*
* @param {ServiceManager} serviceManager
*/
static fromRootHash(serviceManager: ServiceManager): Promise<HashUrl>;
expires: Date;
method: 'GET' | 'PUT' | 'PATCH';
relativePath: string;
url: URL;
constructor(hashPathPayload: HashPathPayload);
get expired(): boolean;
fetch(): Promise<Response>;
}
/**
* { @link HttpClient } which uses web browser `fetch` method to perform HTTP requests.
*/
declare class FetchClient extends HttpClient {
static get(host: string, path: string, headers?: HeadersPayload): Promise<Response>;
static post(host: string, path: string, headers?: HeadersPayload, body?: BodyPayload | null): Promise<Response>;
static patch(host: string, path: string, headers?: HeadersPayload, body?: BodyPayload | null): Promise<Response>;
static put(host: string, path: string, headers?: HeadersPayload, body?: BodyPayload | null): Promise<Response>;
static delete(host: string, path: string, headers?: HeadersPayload): Promise<Response>;
private static makeRequest;
private static request;
}
/**
* { @link HttpClient } which uses Node.js `https` module to perform HTTP requests.
*/
declare class NodeClient extends HttpClient {
static get(host: string, path: string, headers?: HeadersPayload): Promise<Response>;
static post(host: string, path: string, headers?: HeadersPayload, body?: BodyPayload | null): Promise<Response>;
static patch(host: string, path: string, headers?: HeadersPayload, body?: BodyPayload | null): Promise<Response>;
static put(host: string, path: string, headers?: HeadersPayload, body?: BodyPayload | null): Promise<Response>;
static delete(host: string, path: string, headers?: HeadersPayload): Promise<Response>;
private static makeRequest;
private static request;
}
/**
* reMarkable Cloud API client.
*
* Provides an interface to interact with the reMarkable Cloud API:
* - Navigate through the file system
* - Upload documents
*/
declare class RemarkableClient {
#private;
static withFetchHttpClient(deviceToken?: string, sessionToken?: string): RemarkableClient;
static withNodeHttpClient(deviceToken?: string, sessionToken?: string): RemarkableClient;
constructor(deviceToken?: string, sessionToken?: string, httpClientConstructor?: unknown);
get device(): Device;
get session(): Session;
pair(id: string, description: DeviceDescription, oneTimeCode: string): Promise<boolean>;
connect(): Promise<void>;
document(id: string): Promise<Document | undefined>;
folder(id: string): Promise<Folder | undefined>;
upload(name: string, buffer: Buffer): Promise<DocumentReference>;
get sessionExpired(): boolean;
get paired(): boolean;
}
export { Device, type DeviceDescription, Document, DocumentReference, FetchClient, FileBuffer, FileBufferType, FileNotUploadedError, FileSystem, Folder, HashUrl, HttpClient, NodeClient, RemarkableClient, ServiceManager, Session, UnsupportedFileExtensionError };