a-remarkable-js-sdk
Version:
a reMarkable Cloud API wrapper written in TypeScript
10 lines • 13.6 kB
JavaScript
var ne=(i,e,t)=>{if(!e.has(i))throw TypeError("Cannot "+t)};var o=(i,e,t)=>(ne(i,e,"read from private field"),t?t.call(i):e.get(i)),l=(i,e,t)=>{if(e.has(i))throw TypeError("Cannot add the same private member more than once");e instanceof WeakSet?e.add(i):e.set(i,t)},n=(i,e,t,r)=>(ne(i,e,"write to private field"),r?r.call(i,t):e.set(i,t),t);import*as ae from"https";var I,h=class{constructor(e){l(this,I,void 0);n(this,I,e)}get entries(){return o(this,I)}};I=new WeakMap;var f=class{static async get(e,t,r={}){throw new Error("HTTP Client does not implement get static method")}static async post(e,t,r={},s={}){throw new Error("HTTP Client does not implement post static method")}static async patch(e,t,r={},s={}){throw new Error("HTTP Client does not implement patch static method")}static async put(e,t,r={},s={}){throw new Error("HTTP Client does not implement put static method")}static async delete(e,t,r={}){throw new Error("HTTP Client does not implement delete static method")}constructor(e,t={}){this.context={host:e,headers:new h(t)}}async get(e,t={}){let r=this.requestContext(t);return await this.classReference().get(r.host,e,r.headers.entries)}async post(e,t={},r={}){let s=this.requestContext(r);return await this.classReference().post(s.host,e,s.headers.entries,t)}async patch(e,t={},r={}){let s=this.requestContext(r);return await this.classReference().patch(s.host,e,s.headers.entries,t)}async put(e,t={},r={}){let s=this.requestContext(r);return await this.classReference().put(s.host,e,s.headers.entries,t)}async delete(e,t={}){let r=this.requestContext(t);return await this.classReference().delete(r.host,e,r.headers.entries)}classReference(){return this.constructor}requestContext(e){return{host:this.context.host,headers:new h({...this.context.headers.entries,...new h(e).entries})}}};var Q=class extends Error{constructor(e){super(e),this.name="InvalidBodyError"}},U,N,X=class X{constructor(e){l(this,U,void 0);l(this,N,void 0);n(this,U,e),n(this,N,X.serialize(e))}static serialize(e){switch(typeof e){case"object":return e instanceof ArrayBuffer||e instanceof Buffer?e:JSON.stringify(e);case"string":return e;default:throw new Q(`
${e.constructor.name} Http body payload not supported.
Supported types are: ArrayBuffer, Buffer, String & Record.
`)}}get content(){return o(this,U)}get serialized(){return o(this,N)}};U=new WeakMap,N=new WeakMap;var $=X;var O,P=class{constructor(e,t,r,s=null,d=null){this.headers=null;l(this,O,null);this.method=r,this.url=new URL(t,e),s!=null&&(this.headers=new h(s)),d!=null&&n(this,O,new $(d))}get body(){return o(this,O)?.serialized}};O=new WeakMap;var c=class extends f{static async get(e,t,r={}){return await this.makeRequest(this.request(e,t,"GET",r,null))}static async post(e,t,r={},s={}){return await this.makeRequest(this.request(e,t,"POST",r,s))}static async patch(e,t,r={},s={}){return await this.makeRequest(this.request(e,t,"PATCH",r,s))}static async put(e,t,r={},s={}){return await this.makeRequest(this.request(e,t,"PUT",r,s))}static async delete(e,t,r={}){return await this.makeRequest(this.request(e,t,"DELETE",r,null))}static async makeRequest(e){return await new Promise((t,r)=>{let s=e.url.pathname;e.url.search!=null&&(s+=e.url.search);let d=ae.request({method:e.method,hostname:e.url.hostname,path:s,headers:e.headers.entries},p=>{let a="";p.on("data",A=>a+=A),p.on("end",()=>t(new Response(a,{status:p.statusCode,statusText:p.statusMessage})))});d.on("error",p=>{r(p)}),e.body!=null&&d.write(e.body),d.end()})}static request(e,t,r,s={},d={}){return new P(e,t,r,s,d)}};var de="https://service-manager-production-dot-remarkable-production.appspot.com",pe={documentStorage:"/service/json/1/document-storage?environment=production&group=auth0%7C5a68dc51cb30df3877a1d7c4&apiVer=2"},u=class{static productionHttpClient(e={},t=c){return new t("https://webapp-prod.cloud.remarkable.engineering",e)}constructor(e,t=c){this.session=e,this.httpClient=new t(de,{Authorization:`Bearer ${this.session.token}`})}async documentStorageHttpClient(){return await this.serviceHttpClient("documentStorage")}async internalCloudHttpClient(){return await new Promise((e,t=()=>{})=>{e(new c("https://internal.cloud.remarkable.com",{Authorization:`Bearer ${this.session.token}`})),t(new Error("Not implemented"))})}async serviceHttpClient(e){let t=await this.httpClient.get(pe[e]);if(t.status!==200)throw new Error(`Failed to find Remarkable API ${e} Service:
${t.statusText} - ${await t.text()}`);let r=await t.json();return new c(`https://${r.Host}`,{Authorization:`Bearer ${this.session.token}`})}};import{jwtDecode as le}from"jwt-decode";var Z={deviceId:"device-id",deviceDescription:"device-desc"},ee=class extends Error{},x=class i{static valid(e){let t=le(e);return Object.values(Z).every(r=>Object.keys(t).includes(r))}constructor(e){let t=le(e);if(!i.valid(e))throw new ee(`
Invalid reMarkable Cloud API token. A valid reMarkable
Cloud API token contains a device ID and description.
The current token does contains ${Object.keys(t).join(", ")} fields.
`);this.deviceId=t[Z.deviceId],this.deviceDescription=t[Z.deviceDescription],this.token=e,this.tokenIssuedTimestamp=t.iat,this.tokenExpirationTimestamp=t.exp}};var m=class{constructor(e){let t=new x(e);this.deviceId=t.deviceId,this.expiresAt=new Date(t.tokenExpirationTimestamp),this.token=e}get expired(){return Date.now()>=this.expiresAt.getTime()}};var y=class i{static async pair(e,t,r,s=c){let p=await u.productionHttpClient({},s).post("/token/json/2/device/new",{code:r,deviceID:e,deviceDesc:t});if(p.status!==200)throw new Error(`Failed to pair with Remarkable API: ${p.statusText}`);return new i(await p.text())}constructor(e,t=c){let r=new x(e);this.id=r.deviceId,this.description=r.deviceDescription,this.token=e,this.httpClient=u.productionHttpClient({Authorization:`Bearer ${this.token}`},t)}async connect(){let e=await this.httpClient.post("/token/json/2/user/new",{});if(e.status!==200)throw new Error(`Failed to connect with Remarkable API: ${await e.text()}`);return new m(await e.text())}};var v=class{constructor(e,t,r,s,d,p,a){this.id=e,this.hash=t,this.name=r,this.fileType=s,this.lastModified=d,this.lastOpened=p,this.folder=a}};import{TextEncoder as he}from"@polkadot/x-textencoder";import{promises as me}from"fs";import{fromByteArray as fe}from"base64-js";var z=class extends Error{},ce={pdf:[37,80,68,70],epub:[80,75,3,4]},ue={pdf:"application/pdf",epub:"application/epub+zip"},b=class i{static extension(e){let t=new Uint8Array(e).slice(0,4);for(let[r,s]of Object.entries(ce))if(t.every((d,p)=>d===s[p]))return r;throw new z("Unsupported file extension. Only .pdf and .epub files are supported.")}static mimeType(e){let t=this.extension(e);return ue[t]}constructor(e){this.extension=i.extension(e),this.mimeType=i.mimeType(e)}};var j=class extends Error{},L=class{constructor(e,t){this.id=e,this.hash=t}},C=class i{static async fromLocalFile(e,t){let r=e.split("/").pop(),s=await me.readFile(e);return new i(r,s,t)}static async upload(e,t,r){let s=new i(e,t,r);return await s.upload(),s}constructor(e,t,r){this.name=e,this.buffer=t,this.serviceManager=r,this.type=new b(t)}get uploaded(){return this.documentReference!=null}async upload(){let t=await(await this.internalCloudHttpClient()).post("/doc/v2/files",this.buffer,{"content-type":this.type.mimeType,"rm-meta":this.encodedName,"rm-source":"RoR-Browser"});if(t.status!==201)throw new j(`Failed to upload file to reMarkable Cloud: ${await t.text()}`);let r=await t.json();return this.documentReference=new L(r.docID,r.hash),this.documentReference}async internalCloudHttpClient(){return this.httpClient||(this.httpClient=await this.serviceManager.internalCloudHttpClient()),this.httpClient}get encodedName(){let e=new he,t=JSON.stringify({file_name:this.name}),r=e.encode(t);return fe(r)}};var H=class{constructor(e,t,r,s,d,p,a){this.folders=[];this.documents=[];this.id=e,this.hash=t,this.name=r,this.lastModified=s,this.parentFolder=d,this.folders=p,this.documents=a}get root(){return!this.parentFolder}};var S,R,T=class{constructor(e,t){l(this,S,void 0);l(this,R,void 0);n(this,S,e),n(this,R,t)}get documents(){return o(this,S)}get folders(){return o(this,R)}get rootFolder(){return o(this,R).find(e=>e.parentFolder==null)}document(e){return o(this,S).find(t=>t.id===e)}folder(e){return o(this,R).find(t=>t.id===e)}};S=new WeakMap,R=new WeakMap;var B,D,G,_,te=class{constructor(e){l(this,B,void 0);l(this,D,void 0);l(this,G,void 0);l(this,_,void 0);n(this,B,e.filter(t=>t.type==="DocumentType")),n(this,D,e.filter(t=>t.type==="CollectionType")),n(this,_,o(this,D).map(t=>new H(t.id,t.hash,t.visibleName,t.lastModified!=null?new Date(t.lastModified):null,void 0,[],[]))),n(this,G,o(this,B).map(t=>new v(t.id,t.hash,t.visibleName,t.fileType,t.lastModified!=null?new Date(t.lastModified):null,t.lastOpened!=null?new Date(t.lastOpened):null,null))),this.buildFileTreeHierarchy()}get documents(){return o(this,G)}get folders(){return o(this,_)}buildFileTreeHierarchy(){o(this,D).forEach(e=>{let t=this.folders.find(a=>a.id===e.id);e.parent!=null&&t.parentFolder==null&&(t.parentFolder=this.folders.find(a=>a.id===e.parent)),o(this,B).filter(a=>a.parent===t.id).map(a=>this.documents.find(A=>A.id===a.id)).forEach(a=>{a.folder=t,t.documents.push(a)}),o(this,D).filter(a=>a.parent===t.id).map(a=>this.folders.find(A=>A.id===a.id)).forEach(a=>{a.parentFolder=t,t.folders.push(a)})})}};B=new WeakMap,D=new WeakMap,G=new WeakMap,_=new WeakMap;var J,k,g=class{constructor(e){l(this,J,void 0);l(this,k,null);n(this,J,e)}get lastFetchSnapshot(){return o(this,k)}async snapshot(){return n(this,k,await this.fetchSnapshot()),o(this,k)}async document(e){return(await this.snapshot()).documents.find(r=>r.id===e)}async folder(e){return(await this.snapshot()).folders.find(r=>r.id===e)}async fetchSnapshot(){let t=await(await o(this,J).internalCloudHttpClient()).get("/doc/v2/files",{"rm-source":"RoR-Browser"});if(t.status!==200)throw new Error(`Error during file system initialization: ${await t.text()}`);let r=JSON.parse(await t.text()),s=new te(r);return new T(s.documents,s.folders)}};J=new WeakMap,k=new WeakMap;var re=class extends Error{},se=class extends Error{},ie=class extends Error{},oe=class extends Error{},V=class i{static async fromHash(e,t){let s=await(await t.internalCloudHttpClient()).post("/sync/v2/signed-urls/downloads",{http_method:"GET",relative_path:e});if(s.status!==200)throw new re(`Error during hash ${e} download URL request: ${await s.text()}`);return new i(await s.json())}static async fromRootHash(e){let s=await(await(await this.fromHash("root",e)).fetch()).text();return await this.fromHash(s,e)}constructor(e){this.expires=new Date(Date.parse(e.expires)),this.method=e.method,this.relativePath=e.relative_path,this.url=new URL(e.url)}get expired(){return this.expires.getTime()<new Date().getTime()}async fetch(){if(this.expired)throw new oe(`Error during hashUrl ${this.relativePath} content download request: link has expired`);let e=null;switch(this.method){case"GET":e=await c.get(this.url.href,this.url.pathname+this.url.search);break;default:throw new ie(`Error during hashUrl ${this.relativePath} content download request: method ${this.method} not supported`)}if(e.status!==200)throw new se(`Error during hashUrl ${this.relativePath} content download request: ${await e.text()}`);return e}};var E=class extends f{static async get(e,t,r={}){return await this.makeRequest(this.request(e,t,"GET",r,null))}static async post(e,t,r={},s={}){return await this.makeRequest(this.request(e,t,"POST",r,s))}static async patch(e,t,r={},s={}){return await this.makeRequest(this.request(e,t,"PATCH",r,s))}static async put(e,t,r={},s={}){return await this.makeRequest(this.request(e,t,"PUT",r,s))}static async delete(e,t,r={}){return await this.makeRequest(this.request(e,t,"DELETE",r,null))}static async makeRequest(e){return await new Promise(async(t,r)=>{let s=await fetch(e.url.toString(),{method:e.method,headers:e.headers.entries,body:e.body}),d=await s.text();s.ok?t(new Response(d,{status:s.status,statusText:s.statusText})):r(new Error(`HTTP error: ${s.status} - ${d}`))})}static request(e,t,r,s={},d={}){return new P(e,t,r,s,d)}};var K,M,F,w,q,W=class W{constructor(e,t,r=c){l(this,K,void 0);l(this,M,void 0);l(this,F,void 0);l(this,w,void 0);l(this,q,void 0);e!=null?(n(this,M,new y(e)),t!=null&&(n(this,q,new m(t)),n(this,w,new u(this.session,r)),n(this,F,new g(o(this,w))))):n(this,K,r)}static withFetchHttpClient(e,t){return new W(e,t,E)}static withNodeHttpClient(e,t){return new W(e,t,c)}get device(){return o(this,M)}get session(){return o(this,q)}async pair(e,t,r){return this.paired||n(this,M,await y.pair(e,t,r,o(this,K))),this.paired}async connect(){this.sessionExpired&&(n(this,q,await this.device.connect()),n(this,w,new u(this.session)),n(this,F,new g(o(this,w))))}async document(e){return await o(this,F).document(e)}async folder(e){return await o(this,F).folder(e)}async upload(e,t){return await new C(e,t,o(this,w)).upload()}get sessionExpired(){return this.session==null||this.session.expired}get paired(){return this.device!=null}};K=new WeakMap,M=new WeakMap,F=new WeakMap,w=new WeakMap,q=new WeakMap;var Y=W;export{y as Device,v as Document,L as DocumentReference,E as FetchClient,C as FileBuffer,b as FileBufferType,j as FileNotUploadedError,g as FileSystem,H as Folder,V as HashUrl,f as HttpClient,c as NodeClient,Y as RemarkableClient,u as ServiceManager,m as Session,z as UnsupportedFileExtensionError};
//# sourceMappingURL=index.js.map