dome-embedded-app-sdk
Version:
Use this SDK to build plugins for Dome. There are two plugins supported:
2 lines • 15.3 kB
JavaScript
!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.domeSdk=t():e.domeSdk=t()}(this,(()=>(()=>{"use strict";var e={d:(t,s)=>{for(var n in s)e.o(s,n)&&!e.o(t,n)&&Object.defineProperty(t,n,{enumerable:!0,get:s[n]})},o:(e,t)=>Object.prototype.hasOwnProperty.call(e,t),r:e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})}},t={};e.r(t),e.d(t,{CardSdk:()=>c,CryptoA01:()=>n,ViewerSdk:()=>d});const s="0.1.4";class n{constructor(){if(this.subtleCrypto=window.crypto?.subtle,!this.subtleCrypto)throw new Error("SubtleCrypto API is not available in this environment.")}async decrypt(e,t,s){try{if(!e)throw new Error("Invalid token");const n=this.base64UrlDecode(e);if(128!==n[0])throw new Error("Invalid version");n.slice(1,9);const i=n.slice(9,25),r=n.slice(25,-32),o=n.slice(-32),a=await this.deriveKey(t,s),{hmacKey:d,aesKey:c}=await this.splitKey(a),l=n.slice(0,-32);if(!new Uint8Array(await this.subtleCrypto.sign("HMAC",d,l)).every(((e,t)=>e===o[t])))throw new Error("Invalid HMAC. Token has been tampered with!");const h=await this.subtleCrypto.decrypt({name:"AES-CBC",iv:i},c,r);return(new TextDecoder).decode(h)}catch(e){throw console.log("Error in decrypt:",e),e}}async deriveKey(e,t,s=1e4){const n=new TextEncoder,i=await this.subtleCrypto.importKey("raw",n.encode(e),"PBKDF2",!1,["deriveKey"]);return this.subtleCrypto.deriveKey({name:"PBKDF2",hash:"SHA-256",salt:n.encode(t),iterations:s},i,{name:"AES-CBC",length:256},!0,["encrypt","decrypt"])}async splitKey(e){const t=new Uint8Array(await this.subtleCrypto.exportKey("raw",e));return{hmacKey:await this.subtleCrypto.importKey("raw",t.slice(0,16),{name:"HMAC",hash:"SHA-256"},!1,["sign","verify"]),aesKey:await this.subtleCrypto.importKey("raw",t.slice(16),{name:"AES-CBC"},!1,["encrypt","decrypt"])}}base64UrlDecode(e){const t=e.replace(/-/g,"+").replace(/_/g,"/"),s=atob(t);return new Uint8Array([...s].map((e=>e.charCodeAt(0))))}}var i,r,o;!function(e){e.CONNECTION_SUCCESS="CONNECTION_SUCCESS",e.INIT="INIT",e.REQUEST_SAVE="REQUEST_SAVE",e.REQUEST_CLOSE="REQUEST_CLOSE",e.REQUEST_INITIAL_DATA="REQUEST_INITIAL_DATA",e.SET_DIRTY="SET_DIRTY",e.SEND_CLOSE="SEND_CLOSE",e.SEND_EXCEPTION="SEND_EXCEPTION"}(i||(i={})),function(e){e.CONNECT="CONNECT",e.REQUEST_CLOSE="REQUEST_CLOSE",e.REQUEST_SAVE="REQUEST_SAVE",e.SAVE_ERROR="SAVE_ERROR",e.SAVE_SUCCESS="SAVE_SUCCESS",e.DATA_CHANGE="DATA_CHANGE",e.FILE_DATA="FILE_DATA",e.WRITE_FILE_ACK="WRITE_FILE_ACK",e.READ_FILE_ACK="READ_FILE_ACK",e.INIT_ACK="INIT_ACK",e.ERROR="ERROR",e.REFRESH="REFRESH"}(r||(r={})),new Set("undefined"==typeof window?[]:[window.location.origin,"https://dome.so","https://spaces.intouchapp.com/","http://localhost:4200","http://localhost:4201","null"]);class a{constructor(){this.targetOrigin="*",this.isAppReady=!1,this.port2=null,this.platform="unknown",this.detectPlatform()}detectPlatform(){void 0!==window.AndroidBridge?this.platform="android":void 0!==window.webkit?this.platform="ios":this.platform="web",console.debug(`Detected platform: ${this.platform}`)}sendMessage(e,t){const s={type:e,data:t??null};switch(this.platform){case"android":window.AndroidBridge?.sendMessage(JSON.stringify(s));break;case"ios":window?.webkit?.messageHandlers?window.webkit?.messageHandlers.appHandler.postMessage(JSON.stringify(s)):console.error("webkit.messageHandlers not found");break;case"web":this.port2?this.port2.postMessage(s):console.error("Web connection is not established.");break;default:console.error("Unsupported platform, cannot send message.")}console.debug(`Sent message to ${this.platform}:`,s)}sendAppInit(){this.isAppReady||(this.isAppReady=!0,this.sendMessage(i.INIT,{sdk:{ver:s}}))}safeInvoke(e,t,s){const n=t[e];"function"==typeof n?n(s):console.warn(`Handler for '${String(e)}' is not defined.`)}setupParentConnection(){return new Promise(((e,t)=>{switch(this.platform){case"android":window.receiveFromAndroid=e=>{console.debug("Message received from Android:",e),this.handleMessage(e.type,e.data)},e();break;case"ios":window.receiveFromIOS=e=>{console.debug("Message received from iOS:",e),this.handleMessage(e.type,e.data)},e();break;case"web":this.port2&&(console.warn("Connection already established. Skipping reinitialization."),e());const s=t=>{const{type:n}=t.data||{};n===r.CONNECT&&t.ports&&t.ports.length>0&&(this.port2=t.ports[0],this.port2.onmessage=e=>this.handlePortMessage(e),window.removeEventListener("message",s),this.notifyConnectionSuccess(),e())};window.addEventListener("message",s);break;default:console.error("Unknown platform."),t("Unknown platform")}}))}notifyConnectionSuccess(){this.sendMessage(i.CONNECTION_SUCCESS)}handlePortMessage(e){const{type:t,data:s}=e.data||{};t&&this.handleMessage(t,s)}handleMessage(e,t){throw new Error("Subclasses must implement handleMessage.")}}class d extends a{constructor(){super(),this.handler=null,this.pendingRequests=new Map,this.pendingInitAck=null}static init(e){return console.debug("init called",e&&"with handler"),d.initialized?(console.warn("ViewerSdk is already initialized. Skipping initialization."),d.instance):(d.instance||(d.instance=new d,d.instance.setupParentConnection().then((()=>{try{d.instance.initializeViewerSdk()}catch(e){console.error("Error in initializeViewerSdk:",e)}})).catch((e=>{console.error("init: Error setting up parent connection!",e),console.trace("called from:")}))),e&&d.instance.setHandler(e),d.initialized=!0,d.instance)}setHandler(e){this.handler=e,this.pendingInitAck&&(console.debug("Processing pending INIT_ACK message after handler is set."),this.safeInvoke("onInitialData",this.handler,this.pendingInitAck),this.pendingInitAck=null)}canRead(e){return!!e?.includes("r")}canWrite(e){return!!e?.includes("w")||!!e?.includes("*")}initializeViewerSdk(){console.debug("initializing viewer sdk"),this.sendAppInit()}requestInitialData(){this.sendMessage(i.REQUEST_INITIAL_DATA)}requestSave(e,t){const s=function(){if("undefined"!=typeof window&&window.crypto&&window.crypto.randomUUID)return window.crypto.randomUUID();if("undefined"!=typeof crypto&&crypto.randomUUID)return crypto.randomUUID();throw new Error("UUID generation is not supported in this environment")}();return this.sendMessage(i.REQUEST_SAVE,{doc:e,isDataDirty:t,requestId:s}),new Promise(((e,t)=>{this.pendingRequests.set(s,e),this.pendingRequests.set(s+"_reject",t),setTimeout((()=>{this.pendingRequests.has(s)&&(this.pendingRequests.delete(s),this.pendingRequests.delete(s+"_reject"))}),3e4)}))}handleOnSave(e){const{requestId:t,status:s,message:n}=e,i=this.pendingRequests.get(t),r=this.pendingRequests.get(t+"_reject");i&&("error"===s?r?.({status:s,message:n}):i({status:s,message:n}),this.pendingRequests.delete(t),this.pendingRequests.delete(t+"_reject"))}setDirty(e){this.sendMessage(i.SET_DIRTY,e)}sendClose(e,t){this.sendMessage(i.SEND_CLOSE,{doc:e,isDataDirty:t})}sendException(e){this.sendMessage(i.SEND_EXCEPTION,e)}handleMessage(e,t){if(console.debug("handleMessage called for:",e,"with data:",t),this.handler)switch(e){case r.INIT_ACK:this.safeInvoke("onInitialData",this.handler,t);break;case r.DATA_CHANGE:this.safeInvoke("onDataChange",this.handler,t);break;case r.REQUEST_CLOSE:this.safeInvoke("onCloseRequest",this.handler);break;case r.REQUEST_SAVE:this.safeInvoke("onSaveRequest",this.handler);break;case r.SAVE_SUCCESS:case r.SAVE_ERROR:this.handleOnSave(t);break;default:console.warn(`No handler found for message type: ${e}`)}else e===r.INIT_ACK?(console.warn("Handler not set. Storing INIT_ACK message for later processing."),this.pendingInitAck=t):console.error("Message handler not found for type:",e)}}d.initialized=!1,function(e){e.APP_READY="APP_READY",e.INIT="INIT",e.READ_FILE="READ_FILE",e.WRITE_FILE="WRITE_FILE",e.FILE_DIRTY="FILE_DIRTY"}(o||(o={}));class c extends a{constructor(){super(),this.handler=null,this.accessToken="",this.cardFS={readFile:this._readFile.bind(this),writeFile:this._writeFile.bind(this)},this.fileReadResolvers=new Map,this.fileWriteResolvers=new Map,this.pendingAcks=new Map,this.handleMessage=(e,t)=>{if(!this.handler)throw new Error("Message handler not found!");const s=t?.messageId;if(s&&this.pendingAcks.has(s)){const{resolve:e,timeout:n}=this.pendingAcks.get(s);return clearTimeout(n),this.pendingAcks.delete(s),void e(t)}switch(e){case r.INIT_ACK:console.debug("CardSdk: INIT_ACK received"),this.dataStore.kw1=t.key_wa1,this.dataStore.iuid=t.iuid;try{this.cryptoA01.decrypt(this.dataStore.denc,t.key_wa1+this.dataStore.kw2,t.iuid).then((e=>{console.debug("CardSdk: INIT_ACK: decrypted data ",e);const s=JSON.parse(e);this.handler&&this.safeInvoke("onInit",this.handler,{...s,ui:t.ui}),delete this.dataStore.denc})).catch((e=>{throw console.error("Final decrypt error",e),e}))}catch(e){console.error("Decryption failed!",e),this.sendEventError("dec2_failed",e.message)}break;case r.FILE_DATA:const s=t?.name;if(s&&this.fileReadResolvers.has(s)){const e=this.fileReadResolvers.get(s);this.fileReadResolvers.delete(s),e(t)}else console.warn("CardSdk: FILE_DATA received but no resolver found for",s);break;case r.DATA_CHANGE:this.safeInvoke("onFileChange",this.handler,t);case r.SAVE_SUCCESS:const n=t?.name;if(n&&this.fileWriteResolvers.has(n)){const{resolve:e}=this.fileWriteResolvers.get(n);this.fileWriteResolvers.delete(n),e()}else console.warn("CardSdk: SAVE_SUCCESS received but no resolver found for",n);break;case r.SAVE_ERROR:const i=t?.name;if(i&&this.fileWriteResolvers.has(i)){const{reject:e}=this.fileWriteResolvers.get(i);this.fileWriteResolvers.delete(i),e(new Error(t?.message||"Unknown write error"))}else console.warn("CardSdk: SAVE_ERROR received but no resolver found for",i);break;case r.ERROR:this.safeInvoke("onError",this.handler,t);break;case r.REFRESH:this.safeInvoke("onRefreshRequest",this.handler,t);break;default:console.warn(`No handler found for message type: ${e}`)}},this.cryptoA01=new n,console.debug("CardSdk::constructor: done")}static async init(e,t,s){try{return console.debug("CardSdk::init"),c.instance||(c.instance=new c,c.instance.setupParentConnection().then((()=>{c.instance.initializeCardSdk(e)})).catch((e=>{console.error(e)})),t&&c.instance.setHandler(t)),c.instance}catch(e){throw console.error("CardSdk: Unrecoverable error in init",e),e}}setHandler(e){this.handler=e}async initializeCardSdk(e){let t="CardSdk::initializeCardSdk:";try{if(console.debug(t,"enter"),!e)throw new Error("Invalid secret");const s=window.IT_DATA_AF1;if(!s)throw console.error(t,"No data"),new Error("No data");const n=window.location.href;console.debug(t,"url:",n);const i=new URL(n).pathname.split("/").filter((e=>e));let r;if(i.length>1)r=i[i.length-2];else{if(1!=i.length)throw new Error("Invalid URL");r=i[0]}const o=r.split("").reverse().join("").substring(4,25);if(console.debug(t,"ss:",o),!o)throw new Error("Cannot decrypt (1)");const a=await this.cryptoA01.decrypt(s,e,o);try{const e=JSON.parse(a);if(console.debug("CardSdk: dataFromServer:",e),!e.ite)throw new Error("Invalid data");this.dataStore={denc:e.d,kw2:e.kw2},this.accessToken=e.ite,c.instance.sendInit(e.ite)}catch(e){throw console.error("Initial Decryption failed (2):",e),e}}catch(e){console.error(t,"Init failed:",e),this.sendEventError("init_failed",e.message)}}async sendInit(e){this.sendMessage(o.INIT,{token:e,sdk:{ver:s}})}sendMessageWithAck(e,t,s,n=15e3){const i=crypto.randomUUID();return new Promise(((r,o)=>{const a=setTimeout((()=>{this.pendingAcks.delete(i),o(new Error(`${s} not received in time`))}),n);this.pendingAcks.set(i,{resolve:r,reject:o,timeout:a}),this.sendMessage(e,{...t,messageId:i})}))}_readFile(e,t){return new Promise(((s,n)=>{if(this.fileReadResolvers.has(t))return void n(new Error(`A read request is already pending for file: ${t}`));const i=setTimeout((()=>{this.fileReadResolvers.delete(t),n(new Error(`Timed out waiting for file: ${t}`))}),15e3);this.fileReadResolvers.set(t,(e=>{clearTimeout(i),s(e)})),this.sendMessageWithAck(o.READ_FILE,{card_iuid:e,name:t},r.READ_FILE_ACK).catch((()=>{clearTimeout(i),this.fileReadResolvers.delete(t),this.getDocumentAttachedToCard(e,t).then(s).catch(n)}))}))}_writeFile(e,t,s){return new Promise(((n,i)=>{if(this.fileWriteResolvers.has(t))return void i(new Error(`A write request is already pending for file: ${t}`));const a=setTimeout((()=>{this.fileWriteResolvers.delete(t),i(new Error(`Write timed out for file: ${t}`))}),15e3);this.fileWriteResolvers.set(t,{resolve:()=>{clearTimeout(a),this.fileWriteResolvers.delete(t),n()},reject:e=>{clearTimeout(a),this.fileWriteResolvers.delete(t),i(e)}}),this.sendMessageWithAck(o.WRITE_FILE,{name:t,data:s},r.WRITE_FILE_ACK).then((()=>{clearTimeout(a),this.fileWriteResolvers.delete(t),n()})).catch((s=>{clearTimeout(a),this.fileWriteResolvers.delete(t),this.postDocumentAttachedToCard(e,document).then((()=>n())).catch(i)}))}))}async convertToJsonFile(e,t="json",s="file.json"){const n=JSON.stringify(e,null,2),i="application/json",r=new Blob([n],{type:i});return new File([r],s,{type:i})}async convertToFile(e,t,s="json",n){if("json"===s)return this.convertToJsonFile(e,"json",t);{const s=new Blob([e],n?{type:n}:void 0);return new File([s],t,{type:s.type})}}async getDocumentAttachedToCard(e,t="default",s){const n=new URL(`/api/v1/documents/attached_to/${e}/`);n.searchParams.set("name",t),s&&n.searchParams.set("ts_m",s.toString());const i=await fetch(n.toString(),{method:"GET",headers:{Authorization:`Bearer ${this.accessToken||""}`,"Content-Type":"application/json"}});if(!i.ok){const e=await i.text();throw new Error(`Fetch failed: ${e}`)}return i.json()}async postDocumentAttachedToCard(e,t){const s=await fetch(`/api/v1/documents/attached_to/${e}/`,{method:"POST",headers:{Authorization:`Bearer ${this.accessToken||""}`,"Content-Type":"application/json"},body:JSON.stringify(t)});if(!s.ok){const e=await s.text();throw new Error(`Post failed: ${e}`)}return s.json()}async getAllDocumentsAttachedToCard(e){const t=await fetch(`/api/v1/documents/with_card/${e}/`,{method:"GET",headers:{Authorization:`Bearer ${this.accessToken||""}`,"Content-Type":"application/json"}});return await t.json()}async postUploadFile(e,t){const{type:s}=t,n=await this.convertToFile(t,e,s),i=new FormData;i.append("file",n);const r=await fetch("/api/v1/documents/",{method:"POST",headers:{Authorization:`Bearer ${this.accessToken||""}`},body:i});if(!r.ok){const e=await r.text();throw new Error(`Upload failed: ${e}`)}return r.json()}async shareDocumentWithCard(e,t){const s=await fetch(`/api/v1/documents/with_card/${e}/`,{method:"POST",headers:{Authorization:`Bearer ${this.accessToken||""}`,"Content-Type":"application/json"},body:JSON.stringify(t)});if(!s.ok){const e=await s.text();throw new Error(`Share failed: ${e}`)}return s.json()}sendEventError(e,t,s=void 0){let n={message:t,error_code:e,data:s};this.handler&&this.safeInvoke("onError",this.handler,n)}setFileDirty(e){this.sendMessage(o.FILE_DIRTY,e)}getUsername(e){const t=e?.name;return t&&Object.values(t).some((e=>e))?function(e){const{prefix:t="",given:s="",middle:n="",family:i="",suffix:r=""}=e||{};return[t,s,n,i,r].filter((e=>e)).join(" ").trim()}(t):""}}return t})()));
//# sourceMappingURL=index.js.map