remarkable-cloud-js
Version:
reMarkable Cloud API for NodeJs
435 lines (325 loc) • 15.6 kB
Markdown
# 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.