# reMarkable-cloud-js
reMarkable Cloud API for NodeJS

Inspired by
 - Alexander Keil's [unofficial reMarkable Cloud API documentation](https://akeil.de/posts/remarkable-cloud-api/)
 - jmptable's [ReMarkable Tablet Cloud API](https://www.npmjs.com/package/remarkable-tablet-api)
 - ogdentrod's [reMarkable-typescript](https://www.npmjs.com/package/remarkable-typescript)

## Features

  * [X] Authentication
    - [X] device registration
    - [X] user connection
  * [X] data retrieval/push
    - [X] files metadata retrieval
    - [X] folders tree retrieval
    - [X] path exists
    - [X] unlink path
    - [X] create directory
    - [X] move path
    - [X] rename path
    - [X] read/write zip
    - [X] copy path
    - [X] read/write pdf
    - [X] read/write ePub
    - [X] write from url
  * [X] cloud live notifications
    - [X] main data feed (all updates)
    - [X] subscription data feed (specific file/folder updates)

## Main usage

First time authentication

 ```javascript
const RmCJS = require('remarkable-cloud-js')

let rm_api = new RmCJS()

let device_token = await rm_api.register_device('< one time code >', RmCJS.device_desc.desktop.linux)

// save the device_token to be reused later

await rm_api.refresh_token() // auto authentication once registration is done 

 ```

Common connection

 ```javascript
const RmCJS = require('remarkable-cloud-js')

// using the saved device token + refreshing the user token
let rm_api = new RmCJS('< device token >')
await rm_api.refresh_token()
```

Sample storage usage

 ```javascript
const RmCJS = require('remarkable-cloud-js')

let rm_api = new RmCJS('< device token >')
await rm_api.refresh_token()

if(!(await rm_api.exists('/My projects/blueprints'))) {
	await rm_api.mkdir('/My projects/blueprints')
}

let blueprints = await rm_api.get_path_content('/My projects/Articles')

for(let blueprint of blueprints) {
	if(blueprint.VissibleName.includes('to delete')) {
		await rm_api.delete(blueprint._path)
	}
}

await rm.write_pdf('/My projects/Articles/a really cool pdf', './pdfs/article.pdf')

```

Sample notifications usage

 ```javascript
const RmCJS = require('remarkable-cloud-js')

let rm_api = new RmCJS('< device token >')
await rm_api.refresh_token()

function notification_handler(event) {
	console.log('update on', event.document.VissibleName)
}

// ---- event matcher making sure all recieved event come from the remarkable tablet
let notification_matcher = {
	sourceDeviceDesc: 'remarkable'
}

await rm_api.subscribe_to_notifications(notification_handler, notification_matcher)

```
 
## Specifications

### Device types

To use on registration

 - desktop
	- windows (`desktop-windows`)
	- macos (`desktop-macos`)
	- linux (`desktop-linux`)
 - mobile
	- android (`mobile-android`)
	- ios (`mobile-ios`)
 - browser
	- chrome (`browser-chrome`)

found here
```javascript
const RmCJS = require('remarkable-cloud-js')

RmCJS.device_desc

RmCJS.device_desc.desktop
	RmCJS.device_desc.desktop.windows
	RmCJS.device_desc.desktop.macos
	RmCJS.device_desc.desktop.linux

RmCJS.device_desc.mobile
	RmCJS.device_desc.mobile.android
	RmCJS.device_desc.mobile.ios
	
RmCJS.device_desc.browser
	RmCJS.device_desc.browser.chrome

```

### ZIP MAP data representation

In the reMarkable case, ZIP data representing file content often uses the document's ID as a path component. As it is (most of the time) impossible to know this ID in advance, we propose the following zip data representation to use in some APIs arguments:

 - the ZIP MAP object is reprenseted by a flat JSON object.
 - each property represents a path.
	- a path containing the ID uses the `{ID}` string to indicate its position in the path
 - each value can be either a `string`, a `buffer` or a `JSON object`

ZIP MAP sample
```javascript
const fs = require('fs')

let pdf_zip_map = {
	'{ID}.content': {
		extraMetadata: {},
		fileType: file_type,
		lastOpenedPage: 0,
		lineHeight: -1,
		margins: 180,
		pageCount: 0,
		textScale: 1,
		transform: {}
	},
	'{ID}.pagedata': [],
	'{ID}.pdf': fs.readFileSync('< pdf file path >')
}

```

### Document path

The reMarkable document path are absolute and starts with the root folder `/`
 - Sample folder: `/My project/blueprint`
 - Sample document: `/My project/blueprint/project one`

Note that no extension are used in the reMarkable filesystem

### Document types

 - document type (`DocumentType`) represent a "file" (notebook, pdf, epub, etc.)
 - collection type (`CollectionType`) represent a "folder"

found here
```javascript
const RmCJS = require('remarkable-cloud-js')

RmCJS.type

RmCJS.type.document
RmCJS.type.collection

```

### Document representation

(extended from the standard reMarkable representation)

```javascript
{
    ID: '< document UUID >',
    Version: 1,
    Message: '',
    Success: true,
    BlobURLGet: '',
    BlobURLGetExpires: '0001-01-01T00:00:00Z',
    ModifiedClient: '< last modification date string >',
    Type: '< document type >',
    VissibleName: '< document name >',
    CurrentPage: 0,
    Bookmarked: false,
    Parent: '< document parent UUID >',
    _path: '< detected absolute path >'
}
```

#### Standard reMarkable Document representation

The "un-extended document representation" lacks the `_path` component

### Notification event types

 - document added (`DocAdded`) when a document is added, updated (its content) or moved (including to the trash)
 - document deleted (`DocDeleted`) when a document is removed from the cloud (not only trashed)

found here
```javascript
const RmCJS = require('remarkable-cloud-js')

RmCJS.notification.event

RmCJS.notification.event.document_added
RmCJS.notification.event.document_deleted

```

### Notification event data representation

found here
```javascript
{
	auth0UserID: '< unknown data >',
    bookmarked: false,
    event: '< event types >',
    id: '< updating Document UUID >',
    parent: '< updating Document parent UUID >',
    sourceDeviceDesc: '< source device description >',
    sourceDeviceID: '< source device id >',
    type: '< updating Document type >',
    version: '1',
    vissibleName: '< updating Document name >',
    publish_time: '< event occuring time string >',
    document: /* Document representation if event = DocAdded */
}

```

### Exceptions

 - `path_not_found` occurs if a required path cannot be found
 - `update_error` occurs if an error is thrown while updating a document
 - `upload_request_error` occurs if an error is thrown while uploading a document
 - `delete_error` occurs if an error is thrown while deleting a document
 - `path_already_exists_error` occurs if trying to create a path already existing

## API

### Basic data manipulation

#### `exists (path)`
 - **arguments**
	- *`path`* the [path](https://github.com/hugodecasta/remarkable-cloud-js#document-path) to check
 - **output** Boolean value `true` or `false`

#### `unlink (path)`
 - **arguments**
	- *`path`* the [path](https://github.com/hugodecasta/remarkable-cloud-js#document-path) to trash
 - **output** Boolean value `true` or `false`

#### `move (from_path, to_parent)`
 - **arguments**
	- *`from_path`* the moving document's [path](https://github.com/hugodecasta/remarkable-cloud-js#document-path)
	- *`to_parent`* the parent folder's [path](https://github.com/hugodecasta/remarkable-cloud-js#document-path)
 - **output** [Document](https://github.com/hugodecasta/remarkable-cloud-js#document-representation)

#### `rename (path, new_name)`
 - **arguments**
	- *`path`* the renaming document's [path](https://github.com/hugodecasta/remarkable-cloud-js#document-path)
	- *`new_name`* the document's new name
 - **output** [Document](https://github.com/hugodecasta/remarkable-cloud-js#document-representation)


### File content

#### `write_zip (path, zip_map, type)`
 - **arguments**
	- *`path`* the document's [path](https://github.com/hugodecasta/remarkable-cloud-js#document-path) for data writing (can be existing or not)
	- *`zip_map`* the [ZIP MAP](https://github.com/hugodecasta/remarkable-cloud-js#zip-map-data-representation) data
	- *`type`* the [document type](https://github.com/hugodecasta/remarkable-cloud-js#document-types)
 - **output** [Document](https://github.com/hugodecasta/remarkable-cloud-js#document-representation)

#### `read_zip (path)`
 - **arguments**
	- *`path`* the document's [path](https://github.com/hugodecasta/remarkable-cloud-js#document-path) to read
 - **output** [ZIP MAP](https://github.com/hugodecasta/remarkable-cloud-js#zip-map-data-representation) data


#### `mkdir (path)`
 - **arguments**
	- *`path`* the new folder's [path](https://github.com/hugodecasta/remarkable-cloud-js#document-path)
 - **output** Folder ([Document](https://github.com/hugodecasta/remarkable-cloud-js#document-representation))

#### `copy (from_path, to_path)`
 - **arguments**
	- *`from_path`* the copying document's [path](https://github.com/hugodecasta/remarkable-cloud-js#document-path)
	- *`to_path`* the new copyed document's [path](https://github.com/hugodecasta/remarkable-cloud-js#document-path)
 - **output** The copied ([Document](https://github.com/hugodecasta/remarkable-cloud-js#document-representation))


### Specific file content

#### `write_pdf (path, pdf_path, metadata)`
 - **arguments**
	- *`path`* newly added document's [path](https://github.com/hugodecasta/remarkable-cloud-js#document-path)
	- *`pdf_path`* the local PDF file path
	- *`metadata` (optional, `default = {}`)* metadata properties to add to the default PDF file's metadata
 - **output** [Document](https://github.com/hugodecasta/remarkable-cloud-js#document-representation)

#### `write_pdf_from_url (path, pdf_url, metadata)`
 - **arguments**
	- *`path`* newly added document's [path](https://github.com/hugodecasta/remarkable-cloud-js#document-path)
	- *`pdf_url`* the remote PDF file URL
	- *`metadata` (optional, `default = {}`)* metadata properties to add to the default PDF file's metadata
 - **output** [Document](https://github.com/hugodecasta/remarkable-cloud-js#document-representation)

#### `read_pdf (path)`
 - **arguments**
	- *`path`* the existing PDF document's [path](https://github.com/hugodecasta/remarkable-cloud-js#document-path)
 - **output** PDF [Buffer](https://nodejs.org/api/buffer.html) file data

#### `write_epub (path, epub_path, metadata)`
 - **arguments**
	- *`path`* newly added document's [path](https://github.com/hugodecasta/remarkable-cloud-js#document-path)
	- *`epub_path`* the local ePub file path
	- *`metadata` (optional, `default = {}`)* metadata properties to add to the default ePub file's metadata
 - **output** [Document](https://github.com/hugodecasta/remarkable-cloud-js#document-representation)

#### `write_epub_from_url (path, epub_url, metadata)`
 - **arguments**
	- *`path`* newly added document's [path](https://github.com/hugodecasta/remarkable-cloud-js#document-path)
	- *`epub_url`* the remote ePub file URL
	- *`metadata` (optional, `default = {}`)* metadata properties to add to the default ePub file's metadata
 - **output** [Document](https://github.com/hugodecasta/remarkable-cloud-js#document-representation)

#### `read_epub (path)`
 - **arguments**
	- *`path`* the existing ePub document's [path](https://github.com/hugodecasta/remarkable-cloud-js#document-path)
 - **output** ePub [Buffer](https://nodejs.org/api/buffer.html) file data

### Notification API

#### `subscribe_to_notifications (handler, matching_properties)`
 - **arguments**
	- *`handler`* callback function on which to pass the [event](https://github.com/hugodecasta/remarkable-cloud-js#notification-event-data-representation) data
	- *`matching_properties`* subscription properties ([event object](https://github.com/hugodecasta/remarkable-cloud-js#notification-event-data-representation) propeties to filter the incoming events)
 - **output** Boolean value `true`

### Augmented reMarkable API

#### `docs_paths ()`
 - **output** [Document](https://github.com/hugodecasta/remarkable-cloud-js#document-representation) array

#### `get_final_path (path)`
 this method verifies that the path exists
 - **arguments**
	- *`path`* existing document's [path](https://github.com/hugodecasta/remarkable-cloud-js#document-path)
 - **output** [Document](https://github.com/hugodecasta/remarkable-cloud-js#document-representation)

#### `get_ID (id)`
 - **arguments**
	- *`id`* the existing document's UUID
 - **output** [Document](https://github.com/hugodecasta/remarkable-cloud-js#document-representation)

#### `get_name (name)`
 - **arguments**
	- *`name`* the existing document's name
 - **output** [Document](https://github.com/hugodecasta/remarkable-cloud-js#document-representation)

#### `get_path_content (path)`
 - **arguments**
	- *`path`* existing folder's [path](https://github.com/hugodecasta/remarkable-cloud-js#document-path)
 - **output** [Document](https://github.com/hugodecasta/remarkable-cloud-js#document-representation) array

#### `corrupted_docs ()`
 - **output** Parent missing [Document](https://github.com/hugodecasta/remarkable-cloud-js#document-representation) array

#### `trashed_docs ()`
 - **output** [Document](https://github.com/hugodecasta/remarkable-cloud-js#document-representation) array

#### `upload_zip_data (name, parent_path, type, zip_map [, doc])`
 - **arguments**
	- *`name`* the "new or not" document's name
	- *`parent_path`* the "new or not" document's parent's [path](https://github.com/hugodecasta/remarkable-cloud-js#document-path)
	- *`type`* the "new or not" document's [type](https://github.com/hugodecasta/remarkable-cloud-js#document-types)
	- *`zip_map`* the [ZIP MAP](https://github.com/hugodecasta/remarkable-cloud-js#zip-map-data-representation) data to upload
	- *`doc` (optional, `default = null`)* a pre-existing [Document](https://github.com/hugodecasta/remarkable-cloud-js#document-representation)
 - **output** [Document](https://github.com/hugodecasta/remarkable-cloud-js#document-representation)

### base reMarkable API

#### `raw_docs ()`
 - **output** [reMarkable Document](https://github.com/hugodecasta/remarkable-cloud-js#standard-remarkable-document-representation) array

#### `get_doc (ID [,with_blob])`
 - **arguments**
	- *`ID`* document's UUID
	- *`with_blob` (optional, `default = true`)* indicated if the document should come with it's blob dowloading links
 - **output** [reMarkable Document](https://github.com/hugodecasta/remarkable-cloud-js#standard-remarkable-document-representation)

#### `upload_request ([doc])`
 - **arguments**
	- *`doc` (optional, `default = null`)* a pre-existing [reMarkable Document](https://github.com/hugodecasta/remarkable-cloud-js#standard-remarkable-document-representation)
 - **output** [fetch](https://www.npmjs.com/package/node-fetch) json response container this (among others) `ID` and `BlobURLPut`

#### `update_status (doc, changed_doc_data)`
 - **arguments**
	- *`doc`* base [reMarkable Document](https://github.com/hugodecasta/remarkable-cloud-js#standard-remarkable-document-representation) data containing at minimum the `ID` and `Version`
 - **output** the updating sent [reMarkable Document](https://github.com/hugodecasta/remarkable-cloud-js#standard-remarkable-document-representation)

#### `delete (doc)`
 - **arguments**
	- *`doc`* deleting [reMarkable Document](https://github.com/hugodecasta/remarkable-cloud-js#standard-remarkable-document-representation)
 - **output** Boolean value `true` or `false`

## Limitations

Cloud functionalities are not 100% reliable on the tablet and the application, it is thus recommended to use the cloud api with care and if possible with the tablet turned on and connected.