dome-embedded-app-sdk
Version:
Use this SDK to build plugins for Dome. There are two plugins supported:
2 lines • 28.6 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 r in s)e.o(s,r)&&!e.o(t,r)&&Object.defineProperty(t,r,{enumerable:!0,get:s[r]})},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:()=>u,CryptoA01:()=>r,ViewerSdk:()=>d});const s="0.2.0";class r{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 r=this.base64UrlDecode(e);if(128!==r[0])throw new Error("Invalid version");r.slice(1,9);const i=r.slice(9,25),a=r.slice(25,-32),n=r.slice(-32),o=await this.deriveKey(t,s),{hmacKey:c,aesKey:d}=await this.splitKey(o),l=r.slice(0,-32);if(!new Uint8Array(await this.subtleCrypto.sign("HMAC",c,l)).every(((e,t)=>e===n[t])))throw new Error("Invalid HMAC. Token has been tampered with!");const h=await this.subtleCrypto.decrypt({name:"AES-CBC",iv:i},d,a);return(new TextDecoder).decode(h)}catch(e){throw console.log("Error in decrypt:",e),e}}async deriveKey(e,t,s=1e4){const r=new TextEncoder,i=await this.subtleCrypto.importKey("raw",r.encode(e),"PBKDF2",!1,["deriveKey"]);return this.subtleCrypto.deriveKey({name:"PBKDF2",hash:"SHA-256",salt:r.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,a,n;!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.INIT_MESSAGE_CHANNEL="INIT_MESSAGE_CHANNEL"}(a||(a={})),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.DELETE_FILE_ACK="DELETE_FILE_ACK",e.LIST_FILES_ACK="LIST_FILES_ACK",e.INIT_ACK="INIT_ACK",e.ERROR="ERROR",e.REFRESH="REFRESH"}(n||(n={}));new Set("undefined"==typeof window?[]:[window.location.origin,"https://dome.so","https://spaces.intouchapp.com/","http://localhost:4200","http://localhost:4201","null"]);const o=new Set(["dome","intouchapp"]);class c{constructor(){this.targetOrigin="*",this.isAppReady=!1,this.port2=null,this.platform="unknown",this.handleDeepLinkClick=e=>{if("web"!==this.platform)return;if(!(e.target instanceof Element))return;const t=e.target.closest("a[href]");if(!t)return;const s=t.getAttribute("href")??"";this.emitDeepLink(s)&&e.preventDefault()},this.detectPlatform(),this.setupDeepLinkInterception()}detectPlatform(){void 0!==window.AndroidBridge?this.platform="android":void 0!==window.webkit?this.platform="ios":this.platform="web",this.platform}setupDeepLinkInterception(){"web"===this.platform&&"undefined"!=typeof document&&document.addEventListener("click",this.handleDeepLinkClick,!0)}emitDeepLink(e){if("string"!=typeof e||""===e.trim())return console.warn("emitDeepLink called without a valid href"),!1;const t=e.split(":")[0]?.toLowerCase();return!(!t||!o.has(t))&&(this.sendMessage(l.OPEN_DEEPLINK,{url:e}),!0)}getPlatform(){return 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.")}this.platform}sendAppInit(){this.isAppReady||(this.isAppReady=!0,this.sendMessage(i.INIT,{sdk:{ver:s}}))}safeInvoke(e,t,s){const r=t[e];"function"==typeof r?r(s):console.warn(`Handler for '${String(e)}' is not defined.`)}setupParentConnection(){return new Promise(((e,t)=>{switch(this.platform){case"android":window.receiveFromAndroid=e=>{this.handleMessage(e.type,e.data)},e();break;case"ios":window.receiveFromIOS=e=>{this.handleMessage(e.type,e.data)},e();break;case"web":if(this.port2)return console.warn("Connection already established. Skipping reinitialization."),void e();const s=t=>{const{type:r}=t.data||{};r===n.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),window.parent?window.parent.postMessage({type:a.INIT_MESSAGE_CHANNEL},this.targetOrigin):console.error("Parent window not available to initialize message channel.");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 c{constructor(){super(),this.handler=null,this.pendingRequests=new Map,this.pendingInitAck=null}static init(e){return 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&&(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(){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:r}=e,i=this.pendingRequests.get(t),a=this.pendingRequests.get(t+"_reject");i&&("error"===s?a?.({status:s,message:r}):i({status:s,message:r}),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(this.handler)switch(e){case n.INIT_ACK:this.safeInvoke("onInitialData",this.handler,t);break;case n.DATA_CHANGE:this.safeInvoke("onDataChange",this.handler,t);break;case n.REQUEST_CLOSE:this.safeInvoke("onCloseRequest",this.handler);break;case n.REQUEST_SAVE:this.safeInvoke("onSaveRequest",this.handler);break;case n.SAVE_SUCCESS:case n.SAVE_ERROR:this.handleOnSave(t);break;default:console.warn(`No handler found for message type: ${e}`)}else e===n.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)}}var l;d.initialized=!1,function(e){e.APP_READY="APP_READY",e.INIT="INIT",e.READ_FILE="READ_FILE",e.WRITE_FILE="WRITE_FILE",e.DELETE_FILE="DELETE_FILE",e.LIST_FILES="LIST_FILES",e.FILE_DIRTY="FILE_DIRTY",e.OPEN_DEEPLINK="OPEN_DEEPLINK"}(l||(l={}));class h{constructor(e){this.cardIuid=e,this.openPromise=null}isSupported(){return"undefined"!=typeof indexedDB}async openDb(){return this.isSupported()?(this.openPromise||(this.openPromise=new Promise((e=>{const t=indexedDB.open(h.DB_NAME,h.DB_VERSION);t.onupgradeneeded=()=>{const e=t.result;e.objectStoreNames.contains(h.STORE_NAME)||e.createObjectStore(h.STORE_NAME)},t.onsuccess=()=>{const s=t.result;if(!s.objectStoreNames.contains(h.STORE_NAME)){s.close();const t=indexedDB.open(h.DB_NAME,s.version+1);return t.onupgradeneeded=()=>{const e=t.result;e.objectStoreNames.contains(h.STORE_NAME)||e.createObjectStore(h.STORE_NAME)},t.onsuccess=()=>e(t.result),void(t.onerror=()=>e(null))}e(s)},t.onerror=()=>e(null)}))),this.openPromise):null}keyFor(e){return`${this.cardIuid}::${e}`}async put(e,t){const s=await this.openDb();s&&await new Promise((r=>{const i=s.transaction(h.STORE_NAME,"readwrite");i.objectStore(h.STORE_NAME).put(t,e).onsuccess=()=>r(),i.oncomplete=()=>r(),i.onerror=()=>r(),i.onabort=()=>r()}))}async get(e){const t=await this.openDb();if(t)return new Promise((s=>{const r=t.transaction(h.STORE_NAME,"readonly").objectStore(h.STORE_NAME).get(e);r.onsuccess=()=>s(r.result),r.onerror=()=>s(void 0)}))}async delete(e){const t=await this.openDb();t&&await new Promise((s=>{const r=t.transaction(h.STORE_NAME,"readwrite");r.objectStore(h.STORE_NAME).delete(e).onsuccess=()=>s(),r.oncomplete=()=>s(),r.onerror=()=>s(),r.onabort=()=>s()}))}static estimateSize(e){if(null==e)return 0;if("undefined"!=typeof Blob&&e instanceof Blob)return e.size;if(e instanceof ArrayBuffer)return e.byteLength;if(ArrayBuffer.isView(e))return e.byteLength;if("string"==typeof e)return new Blob([e]).size;try{return new Blob([JSON.stringify(e)]).size}catch{return 0}}async cacheDocument(e,t){if(!e?.iuid)return;if(await this.put(this.keyFor(`${e.iuid}_object`),e),await this.put(this.keyFor(`name::${e.name}`),e.iuid),void 0===t)return void await this.delete(this.keyFor(`${e.iuid}_data`));h.estimateSize(t)<=h.MAX_DATA_BYTES?await this.put(this.keyFor(`${e.iuid}_data`),t):await this.delete(this.keyFor(`${e.iuid}_data`))}async getByName(e){const t=await this.get(this.keyFor(`name::${e}`));if(!t)return null;const s=await this.get(this.keyFor(`${t}_object`));if(!s)return null;return{iuid:t,object:s,data:await this.get(this.keyFor(`${t}_data`))}}async deleteByName(e){const t=await this.getByName(e);t&&(await this.delete(this.keyFor(`name::${e}`)),await this.delete(this.keyFor(`${t.iuid}_object`)),await this.delete(this.keyFor(`${t.iuid}_data`)))}async deleteByIuid(e,t){t&&await this.delete(this.keyFor(`name::${t}`)),await this.delete(this.keyFor(`${e}_object`)),await this.delete(this.keyFor(`${e}_data`))}async getAllCachedObjects(){const e=await this.openDb();return e?new Promise((t=>{const s=[],r=this.keyFor(""),i=e.transaction(h.STORE_NAME,"readonly").objectStore(h.STORE_NAME).openCursor();i.onsuccess=e=>{const i=e.target.result;if(!i)return void t(s);const a=i.key;"string"==typeof a&&a.startsWith(r)&&a.endsWith("_object")&&s.push(i.value),i.continue()},i.onerror=()=>t(s)})):[]}}h.DB_NAME="dome-cardfs",h.DB_VERSION=1,h.STORE_NAME="cards",h.MAX_DATA_BYTES=5242880;class u extends c{getCardIuid(){return this.dataStore?.iuid||null}ensureCardFsCache(){if(!this.cardFsCache){const e=this.getCardIuid();e&&(this.cardFsCache=new h(e))}return this.cardFsCache}constructor(){super(),this.handler=null,this.accessToken="",this.fileReadResolvers=new Map,this.fileWriteResolvers=new Map,this.fsReadRequests=new Map,this.fsWriteRequests=new Map,this.cardFsCache=null,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:r}=this.pendingAcks.get(s);return clearTimeout(r),this.pendingAcks.delete(s),void e(t)}switch(e){case n.INIT_ACK:this.dataStore.kw1=t.key_wa1,this.dataStore.iuid=t.iuid,t.iuid&&(this.cardFsCache=new h(t.iuid));try{this.cryptoA01.decrypt(this.dataStore.denc,t.key_wa1+this.dataStore.kw2,t.iuid).then((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 n.FILE_DATA:{const e=t?.messageId;if(e&&this.fsReadRequests.has(e)){const{messageId:s,...r}=t||{};return void this.handleFsReadDataMessage(e,r)}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 n.DATA_CHANGE:this.safeInvoke("onFileChange",this.handler,t);case n.SAVE_SUCCESS:{const e=t?.messageId;if(e&&this.fsWriteRequests.has(e))return void this.handleFsWriteSuccess(e,t);const s=t?.name;if(s&&this.fileWriteResolvers.has(s)){const{resolve:e}=this.fileWriteResolvers.get(s);this.fileWriteResolvers.delete(s),e()}else console.warn("CardSdk: SAVE_SUCCESS received but no resolver found for",s);break}case n.SAVE_ERROR:{const e=t?.messageId;if(e&&this.fsWriteRequests.has(e))return void this.handleFsWriteError(e,t);const s=t?.name;if(s&&this.fileWriteResolvers.has(s)){const{reject:e}=this.fileWriteResolvers.get(s);this.fileWriteResolvers.delete(s),e(new Error(t?.message||"Unknown write error"))}else console.warn("CardSdk: SAVE_ERROR received but no resolver found for",s);break}case n.ERROR:{const e=t?.messageId;if(e){const s=t?.message||"Unknown error";if(this.fsReadRequests.has(e))return void this.failFsReadRequest(e,new Error(s));if(this.fsWriteRequests.has(e))return void this.failFsWriteRequest(e,new Error(s))}this.safeInvoke("onError",this.handler,t);break}case n.REFRESH:this.safeInvoke("onRefreshRequest",this.handler,t);break;default:console.warn(`No handler found for message type: ${e}`)}},this.cryptoA01=new r,this.cardsFS={read:(e,t)=>this.cardsFsRead(e,t),write:(e,t,s)=>this.cardsFsWrite(e,t,s),delete:(e,t)=>this.cardsFsDelete(e,t),list:e=>this.cardsFsList(e)}}static async init(e,t,s){try{return u.instance||(u.instance=new u,u.instance.setupParentConnection().then((()=>{u.instance.initializeCardSdk(e)})).catch((e=>{console.error(e)})),t&&u.instance.setHandler(t)),u.instance}catch(e){throw console.error("CardSdk: Unrecoverable error in init",e),e}}setHandler(e){this.handler=e}openDeepLink(e){this.emitDeepLink(e)||console.warn("openDeepLink ignored; provide a dome:// or intouchapp:// href")}async initializeCardSdk(e){let t="CardSdk::initializeCardSdk:";try{if(!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 r=window.location.href;const i=new URL(r).pathname.split("/wa/").filter(Boolean)[1].split("/")[0].split("").reverse().join("").substring(4,25);if(!i)throw new Error("Cannot decrypt (1)");const a=await this.cryptoA01.decrypt(s,e,i);try{const e=JSON.parse(a);if(!e.ite)throw new Error("Invalid data");this.dataStore={denc:e.d,kw2:e.kw2},this.accessToken=e.ite,u.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(l.INIT,{token:e,sdk:{ver:s}})}sendMessageWithAck(e,t,s,r=15e3,i){const a=crypto.randomUUID();if(i)try{i(a)}catch(e){return Promise.reject(e)}return new Promise(((i,n)=>{const o=setTimeout((()=>{this.pendingAcks.delete(a),n(new Error(`${s} not received in time`))}),r);this.pendingAcks.set(a,{resolve:i,reject:n,timeout:o}),this.sendMessage(e,{...t,messageId:a})}))}cardsFsRead(e,t){return e&&"string"==typeof e?new Promise(((s,r)=>{let i=null;this.sendMessageWithAck(l.READ_FILE,{name:e},n.READ_FILE_ACK,u.CARD_FS_ACK_TIMEOUT_MS,(a=>{i=a;const n={name:e,onUpdate:t,resolve:s,reject:r,hasResolved:!1};n.noResponseTimeout=setTimeout((()=>{this.failFsReadRequest(a,new Error(`cardsFS.read timed out for file: ${e}`))}),u.FS_RESPONSE_TIMEOUT_MS),this.fsReadRequests.set(a,n)})).catch((()=>{this.cardsFsReadFallback(e,i??void 0,t).then((e=>{i||s(e)})).catch((e=>{const t=e instanceof Error?e:new Error(String(e));i?this.failFsReadRequest(i,t):r(t)}))}))})):Promise.reject(new Error("cardsFS.read requires a file name"))}cardsFsWrite(e,t,s){return e&&"string"==typeof e?new Promise(((r,i)=>{let a=null;this.sendMessageWithAck(l.WRITE_FILE,{name:e,data:t},n.WRITE_FILE_ACK,u.CARD_FS_ACK_TIMEOUT_MS,(t=>{a=t;const n={name:e,onResult:s,resolve:r,reject:i,timeout:setTimeout((()=>{this.failFsWriteRequest(t,new Error(`cardsFS.write timed out for file: ${e}`))}),u.FS_RESPONSE_TIMEOUT_MS)};this.fsWriteRequests.set(t,n)})).catch((()=>{this.cardsFsWriteFallback(e,t,a??void 0).then((e=>{if(!a){try{s?.(e)}catch(e){console.error("cardsFS.write fallback callback threw",e)}r(e)}})).catch((e=>{const t=e instanceof Error?e:new Error(String(e));a?this.failFsWriteRequest(a,t):i(t)}))}))})):Promise.reject(new Error("cardsFS.write requires a file name"))}cardsFsDelete(e,t){return e&&"string"==typeof e?new Promise(((s,r)=>{this.sendMessageWithAck(l.DELETE_FILE,{name:e},n.DELETE_FILE_ACK,u.CARD_FS_ACK_TIMEOUT_MS).then((e=>{const{messageId:r,...i}=e||{};t?.(i),s(i)})).catch((()=>{this.cardsFsDeleteFallback(e).then((e=>{try{t?.(e)}catch(e){console.error("cardsFS.delete fallback callback threw",e)}s(e)})).catch((e=>{r(e instanceof Error?e:new Error(String(e)))}))}))})):Promise.reject(new Error("cardsFS.delete requires a file name"))}cardsFsList(e){return new Promise(((t,s)=>{this.sendMessageWithAck(l.LIST_FILES,{},n.LIST_FILES_ACK,u.CARD_FS_ACK_TIMEOUT_MS).then((s=>{const{messageId:r,...i}=s||{};e?.(i),t(i)})).catch((()=>{this.cardsFsListFallback().then((s=>{try{e?.(s)}catch(e){console.error("cardsFS.list fallback callback threw",e)}t(s)})).catch((e=>{s(e instanceof Error?e:new Error(String(e)))}))}))}))}handleFsReadDataMessage(e,t){const s=this.fsReadRequests.get(e);if(s){if(s.noResponseTimeout&&(clearTimeout(s.noResponseTimeout),s.noResponseTimeout=void 0),"function"==typeof s.onUpdate)try{s.onUpdate(t)}catch(e){console.error("cardsFS.read callback threw",e)}s.hasResolved||(s.hasResolved=!0,s.resolve(t)),s.cleanupTimeout&&clearTimeout(s.cleanupTimeout),s.cleanupTimeout=setTimeout((()=>{this.clearFsReadRequest(e)}),u.FS_READ_RETENTION_MS)}}clearFsReadRequest(e){const t=this.fsReadRequests.get(e);if(t)return t.noResponseTimeout&&clearTimeout(t.noResponseTimeout),t.cleanupTimeout&&clearTimeout(t.cleanupTimeout),this.fsReadRequests.delete(e),t}failFsReadRequest(e,t){const s=this.clearFsReadRequest(e);s&&s.reject(t)}clearFsWriteRequest(e){const t=this.fsWriteRequests.get(e);if(t)return clearTimeout(t.timeout),this.fsWriteRequests.delete(e),t}failFsWriteRequest(e,t){const s=this.clearFsWriteRequest(e);s&&s.reject(t)}handleFsWriteSuccess(e,t){const s=this.clearFsWriteRequest(e);if(!s)return;const{messageId:r,...i}=t||{};if("function"==typeof s.onResult)try{s.onResult(i)}catch(e){console.error("cardsFS.write callback threw",e)}s.resolve(i)}handleFsWriteError(e,t){const s=this.clearFsWriteRequest(e);if(!s)return;const r=t instanceof Error?t:new Error(t?.message||"Unknown write error");s.reject(r)}async cardsFsReadFallback(e,t,s){const r=this.getCardIuid();if(!r)throw new Error("cardsFS.read fallback failed: card not initialized");const i=this.ensureCardFsCache();let a=null;const n=e=>{if(e){if(t&&this.fsReadRequests.has(t))this.handleFsReadDataMessage(t,e);else if("function"==typeof s)try{s(e)}catch(e){console.error("cardsFS.read fallback callback threw",e)}a||(a=e)}};if(i)try{const t=await i.getByName(e);t?.object&&n({object:t.object,data:t.data})}catch(e){console.warn("cardsFS.read cache lookup failed",e)}try{const t=await this.fetchDocumentPayload(r,e);t&&(await(i?.cacheDocument(t.object,t.data)),n(t))}catch(e){if(!a)throw e;console.warn("cardsFS.read fresh fetch failed",e)}if(!a)throw new Error(`cardsFS.read fallback could not locate file: ${e}`);return a}async cardsFsWriteFallback(e,t,s){const r=this.getCardIuid();if(!r)throw new Error("cardsFS.write fallback failed: card not initialized");await this.upsertDocumentViaApi(r,e,t);const i=await this.fetchDocumentPayload(r,e);return await(this.ensureCardFsCache()?.cacheDocument(i.object,i.data)),s&&this.fsWriteRequests.has(s)&&this.handleFsWriteSuccess(s,i),i}async cardsFsDeleteFallback(e){const t=this.getCardIuid();if(!t)throw new Error("cardsFS.delete fallback failed: card not initialized");const s=this.ensureCardFsCache(),r=await this.fetchDocumentMetadataByName(t,e).catch((()=>null));if(!r?.iuid)return await(s?.deleteByName(e)),{name:e,deleted:!1};const i=await fetch(`/api/v1/documents/${r.iuid}/`,{method:"DELETE",headers:this.authHeader()});if(!i.ok&&404!==i.status){const e=await i.text().catch((()=>"delete failed"));throw new Error(`cardsFS.delete fallback failed: ${e}`)}return await(s?.deleteByIuid(r.iuid,r.name??e)),{name:e,deleted:!0}}async cardsFsListFallback(){const e=this.getCardIuid();if(!e)throw new Error("cardsFS.list fallback failed: card not initialized");const t=this.ensureCardFsCache();try{const s=await this.fetchAllDocuments(e);if(t)for(const e of s)await t.cacheDocument(e,void 0);return{documents:s}}catch(e){const s=await(t?.getAllCachedObjects());if(s&&s.length>0)return{documents:s,from_cache:!0};throw e}}authHeader(){return this.accessToken?{Authorization:`Bearer ${this.accessToken}`}:{}}jsonHeaders(){return{...this.authHeader(),"Content-Type":"application/json"}}async fetchDocumentMetadataByName(e,t){const s=await fetch(`/api/v1/documents/with_card/${e}/?name=${encodeURIComponent(t)}`,{method:"GET",headers:this.authHeader()});if(!s.ok){if(404===s.status)return null;const e=await s.text().catch((()=>"metadata lookup failed"));throw new Error(`cardsFS metadata fetch failed: ${e}`)}const r=await s.json().catch((()=>null));return Array.isArray(r?.results)?r.results.find((e=>e?.name===t))??r.results[0]??null:Array.isArray(r)?r.find((e=>e?.name===t))??null:r?.name===t?r:r??null}async fetchAllDocuments(e){const t=await fetch(`/api/v1/documents/with_card/${e}/`,{method:"GET",headers:this.authHeader()});if(!t.ok){const e=await t.text().catch((()=>"list failed"));throw new Error(`cardsFS.list failed: ${e}`)}const s=await t.json().catch((()=>null));return Array.isArray(s?.results)?s.results:Array.isArray(s)?s:s?[s]:[]}async fetchDocumentPayload(e,t){const s=await this.fetchDocumentMetadataByName(e,t);if(!s)throw new Error(`cardsFS document not found: ${t}`);return{object:s,data:await this.fetchDocumentData(s)}}async fetchDocumentData(e){const t=e?.orig?.url||e?.url?.original||e?.url;if(!t||"string"!=typeof t)return e?.data??null;const s=await fetch(t,{headers:this.authHeader()});if(!s.ok){const e=await s.text().catch((()=>"download failed"));throw new Error(`cardsFS data fetch failed: ${e}`)}const r=s.headers.get("Content-Type")||"";return r.includes("application/json")?s.json():r.startsWith("text/")?s.text():s.arrayBuffer()}async buildFileFormData(e,t){let s;const r=e=>{if(e instanceof ArrayBuffer)return new Blob([e.slice(0)],{type:"application/octet-stream"});if("undefined"!=typeof SharedArrayBuffer&&e instanceof SharedArrayBuffer){const t=new Uint8Array(e),s=new Uint8Array(t.length);return s.set(t),new Blob([s.buffer],{type:"application/octet-stream"})}if(ArrayBuffer.isView(e)){const t=new Uint8Array(e.buffer,e.byteOffset,e.byteLength),s=new Uint8Array(t.length);return s.set(t),new Blob([s.buffer],{type:"application/octet-stream"})}const t=new Uint8Array(e),s=new Uint8Array(t.length);return s.set(t),new Blob([s.buffer],{type:"application/octet-stream"})};if(t instanceof File)s=t;else if("undefined"!=typeof Blob&&t instanceof Blob)s=new File([t],e,{type:t.type||"application/octet-stream"});else if("undefined"!=typeof SharedArrayBuffer&&t instanceof SharedArrayBuffer){const i=r(t);s=new File([i],e,{type:i.type})}else if(t instanceof ArrayBuffer){const i=r(t);s=new File([i],e,{type:i.type})}else if(ArrayBuffer.isView(t)){const i=r(t);s=new File([i],e,{type:i.type})}else if("string"==typeof t){const r=new Blob([t],{type:"text/plain"});s=new File([r],e,{type:r.type})}else{const r=JSON.stringify(t??{}),i=new Blob([r],{type:"application/json"});s=new File([i],e,{type:i.type})}const i=new FormData;return i.append("file",s),i}async upsertDocumentViaApi(e,t,s){const r=await this.fetchDocumentMetadataByName(e,t).catch((()=>null)),i=await this.buildFileFormData(t,s);if(r?.iuid){const e=await fetch(`/api/v1/documents/${r.iuid}/`,{method:"PUT",headers:this.authHeader(),body:i});if(!e.ok){const t=await e.text().catch((()=>"update failed"));throw new Error(`cardsFS.write update failed: ${t}`)}return}const a=await fetch("/api/v1/documents/",{method:"POST",headers:this.authHeader(),body:i});if(!a.ok){const e=await a.text().catch((()=>"upload failed"));throw new Error(`cardsFS.write upload failed: ${e}`)}const n=await a.json().catch((()=>null)),o=Array.isArray(n?.results)?n.results[0]:n,c=o?.iuid;if(!c)throw new Error("cardsFS.write upload failed: missing document iuid");const d=await fetch(`/api/v1/documents/with_card/${e}/`,{method:"POST",headers:this.jsonHeaders(),body:JSON.stringify({documents:[c]})});if(!d.ok){const e=await d.text().catch((()=>"attach failed"));throw new Error(`cardsFS.write attach failed: ${e}`)}}_readFile(e,t){return new Promise(((s,r)=>{if(this.fileReadResolvers.has(t))return void r(new Error(`A read request is already pending for file: ${t}`));const i=setTimeout((()=>{this.fileReadResolvers.delete(t),r(new Error(`Timed out waiting for file: ${t}`))}),15e3);this.fileReadResolvers.set(t,(e=>{clearTimeout(i),s(e)})),this.sendMessageWithAck(l.READ_FILE,{card_iuid:e,name:t},n.READ_FILE_ACK).catch((()=>{clearTimeout(i),this.fileReadResolvers.delete(t),this.getDocumentAttachedToCard(e,t).then(s).catch(r)}))}))}_writeFile(e,t,s){return new Promise(((r,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),r()},reject:e=>{clearTimeout(a),this.fileWriteResolvers.delete(t),i(e)}}),this.sendMessageWithAck(l.WRITE_FILE,{name:t,data:s},n.WRITE_FILE_ACK).then((()=>{clearTimeout(a),this.fileWriteResolvers.delete(t),r()})).catch((s=>{clearTimeout(a),this.fileWriteResolvers.delete(t),this.postDocumentAttachedToCard(e,document).then((()=>r())).catch(i)}))}))}async getDocumentAttachedToCard(e,t="default"){const s=new URL(`/api/v1/documents/attached_to/${e}/`);s.searchParams.set("name",t);const r=await fetch(s.toString(),{method:"GET",headers:{Authorization:`Bearer ${this.accessToken||""}`,"Content-Type":"application/json"}});if(!r.ok){const e=await r.text();throw new Error(`Fetch failed: ${e}`)}return r.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 getCardDocument(e,t){const s=await fetch(`/api/v1/documents/with_card/${e}/?name=${t}`,{method:"GET",headers:{Authorization:`Bearer ${this.accessToken||""}`,"Content-Type":"application/json"}});return await s.json()}sendEventError(e,t,s=void 0){let r={message:t,error_code:e,data:s};this.handler&&this.safeInvoke("onError",this.handler,r)}setFileDirty(e){this.sendMessage(l.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:r="",family:i="",suffix:a=""}=e||{};return[t,s,r,i,a].filter((e=>e)).join(" ").trim()}(t):""}}return u.FS_RESPONSE_TIMEOUT_MS=3e4,u.FS_READ_RETENTION_MS=5e3,u.CARD_FS_ACK_TIMEOUT_MS=2e3,t})()));
//# sourceMappingURL=index.js.map