@speckle/objectloader
Version:
Simple API helper to stream in objects from the Speckle Server.
2 lines (1 loc) • 17.4 kB
JavaScript
"use strict";require("core-js/modules/es.symbol.description.js"),require("core-js/modules/es.array.flat.js"),require("core-js/modules/es.array.flat-map.js"),require("core-js/modules/es.array.includes.js"),require("core-js/modules/es.array.reduce.js"),require("core-js/modules/es.array.reduce-right.js"),require("core-js/modules/es.array.sort.js"),require("core-js/modules/es.array.unscopables.flat.js"),require("core-js/modules/es.array.unscopables.flat-map.js"),require("core-js/modules/es.math.hypot.js"),require("core-js/modules/es.object.from-entries.js"),require("core-js/modules/es.promise.js"),require("core-js/modules/es.promise.finally.js"),require("core-js/modules/es.regexp.constructor.js"),require("core-js/modules/es.regexp.exec.js"),require("core-js/modules/es.regexp.flags.js"),require("core-js/modules/es.string.replace.js"),require("core-js/modules/es.typed-array.float32-array.js"),require("core-js/modules/es.typed-array.float64-array.js"),require("core-js/modules/es.typed-array.int8-array.js"),require("core-js/modules/es.typed-array.int16-array.js"),require("core-js/modules/es.typed-array.int32-array.js"),require("core-js/modules/es.typed-array.uint8-array.js"),require("core-js/modules/es.typed-array.uint8-clamped-array.js"),require("core-js/modules/es.typed-array.uint16-array.js"),require("core-js/modules/es.typed-array.uint32-array.js"),require("core-js/modules/es.typed-array.fill.js"),require("core-js/modules/es.typed-array.from.js"),require("core-js/modules/es.typed-array.of.js"),require("core-js/modules/es.typed-array.set.js"),require("core-js/modules/es.typed-array.sort.js"),require("core-js/modules/esnext.aggregate-error.js"),require("core-js/modules/esnext.array.last-index.js"),require("core-js/modules/esnext.array.last-item.js"),require("core-js/modules/esnext.composite-key.js"),require("core-js/modules/esnext.composite-symbol.js"),require("core-js/modules/esnext.global-this.js"),require("core-js/modules/esnext.map.delete-all.js"),require("core-js/modules/esnext.map.every.js"),require("core-js/modules/esnext.map.filter.js"),require("core-js/modules/esnext.map.find.js"),require("core-js/modules/esnext.map.find-key.js"),require("core-js/modules/esnext.map.from.js"),require("core-js/modules/esnext.map.group-by.js"),require("core-js/modules/esnext.map.includes.js"),require("core-js/modules/esnext.map.key-by.js"),require("core-js/modules/esnext.map.key-of.js"),require("core-js/modules/esnext.map.map-keys.js"),require("core-js/modules/esnext.map.map-values.js"),require("core-js/modules/esnext.map.merge.js"),require("core-js/modules/esnext.map.of.js"),require("core-js/modules/esnext.map.reduce.js"),require("core-js/modules/esnext.map.some.js"),require("core-js/modules/esnext.map.update.js"),require("core-js/modules/esnext.math.clamp.js"),require("core-js/modules/esnext.math.deg-per-rad.js"),require("core-js/modules/esnext.math.degrees.js"),require("core-js/modules/esnext.math.fscale.js"),require("core-js/modules/esnext.math.iaddh.js"),require("core-js/modules/esnext.math.imulh.js"),require("core-js/modules/esnext.math.isubh.js"),require("core-js/modules/esnext.math.rad-per-deg.js"),require("core-js/modules/esnext.math.radians.js"),require("core-js/modules/esnext.math.scale.js"),require("core-js/modules/esnext.math.seeded-prng.js"),require("core-js/modules/esnext.math.signbit.js"),require("core-js/modules/esnext.math.umulh.js"),require("core-js/modules/esnext.number.from-string.js"),require("core-js/modules/esnext.observable.js"),require("core-js/modules/esnext.promise.all-settled.js"),require("core-js/modules/esnext.promise.any.js"),require("core-js/modules/esnext.promise.try.js"),require("core-js/modules/esnext.reflect.define-metadata.js"),require("core-js/modules/esnext.reflect.delete-metadata.js"),require("core-js/modules/esnext.reflect.get-metadata.js"),require("core-js/modules/esnext.reflect.get-metadata-keys.js"),require("core-js/modules/esnext.reflect.get-own-metadata.js"),require("core-js/modules/esnext.reflect.get-own-metadata-keys.js"),require("core-js/modules/esnext.reflect.has-metadata.js"),require("core-js/modules/esnext.reflect.has-own-metadata.js"),require("core-js/modules/esnext.reflect.metadata.js"),require("core-js/modules/esnext.set.add-all.js"),require("core-js/modules/esnext.set.delete-all.js"),require("core-js/modules/esnext.set.difference.js"),require("core-js/modules/esnext.set.every.js"),require("core-js/modules/esnext.set.filter.js"),require("core-js/modules/esnext.set.find.js"),require("core-js/modules/esnext.set.from.js"),require("core-js/modules/esnext.set.intersection.js"),require("core-js/modules/esnext.set.is-disjoint-from.js"),require("core-js/modules/esnext.set.is-subset-of.js"),require("core-js/modules/esnext.set.is-superset-of.js"),require("core-js/modules/esnext.set.join.js"),require("core-js/modules/esnext.set.map.js"),require("core-js/modules/esnext.set.of.js"),require("core-js/modules/esnext.set.reduce.js"),require("core-js/modules/esnext.set.some.js"),require("core-js/modules/esnext.set.symmetric-difference.js"),require("core-js/modules/esnext.set.union.js"),require("core-js/modules/esnext.string.at.js"),require("core-js/modules/esnext.string.code-points.js"),require("core-js/modules/esnext.string.match-all.js"),require("core-js/modules/esnext.string.replace-all.js"),require("core-js/modules/esnext.symbol.dispose.js"),require("core-js/modules/esnext.symbol.observable.js"),require("core-js/modules/esnext.symbol.pattern-match.js"),require("core-js/modules/esnext.weak-map.delete-all.js"),require("core-js/modules/esnext.weak-map.from.js"),require("core-js/modules/esnext.weak-map.of.js"),require("core-js/modules/esnext.weak-set.add-all.js"),require("core-js/modules/esnext.weak-set.delete-all.js"),require("core-js/modules/esnext.weak-set.from.js"),require("core-js/modules/esnext.weak-set.of.js"),require("core-js/modules/web.dom-collections.iterator.js"),require("core-js/modules/web.immediate.js"),require("core-js/modules/web.queue-microtask.js"),require("core-js/modules/web.url.js"),require("core-js/modules/web.url.to-json.js"),require("core-js/modules/web.url-search-params.js");var e=require("#lodash");function s(e,s,t){return(s=function(e){var s=function(e,s){if("object"!=typeof e||!e)return e;var t=e[Symbol.toPrimitive];if(void 0!==t){var r=t.call(e,s||"default");if("object"!=typeof r)return r;throw new TypeError("@@toPrimitive must return a primitive value.")}return("string"===s?String:Number)(e)}(e,"string");return"symbol"==typeof s?s:s+""}(s))in e?Object.defineProperty(e,s,{value:t,enumerable:!0,configurable:!0,writable:!0}):e[s]=t,e}class t extends Error{constructor(e,s){e||(e=new.target.defaultMessage),super(e,s)}}s(t,"defaultMessage","Unexpected error occurred");class r extends t{}s(r,"defaultMessage","Object loader configured incorrectly!");class o extends t{}s(o,"defaultMessage","Object loader encountered a runtime problem!");class i{constructor(e){var s;let{serverUrl:t,streamId:i,token:n,objectId:a,options:c={enableCaching:!0,fullyTraverseArrays:!1,excludeProps:[],fetch:null,customLogger:void 0,customWarner:void 0}}=e;if(this.logger=c.customLogger||console.log,this.warner=c.customWarner||console.warn,this.INTERVAL_MS=20,this.TIMEOUT_MS=18e4,this.serverUrl=t||(null===globalThis||void 0===globalThis||null===(s=globalThis.location)||void 0===s?void 0:s.origin),!this.serverUrl)throw new r("Invalid serverUrl specified!");if(this.streamId=i,this.objectId=a,!this.streamId)throw new r("Invalid streamId specified!");if(!this.objectId)throw new r("Invalid objectId specified!");this.logger("Object loader constructor called!"),this.token=n,this.headers={Accept:"text/plain"},this.token&&(this.headers.Authorization="Bearer ".concat(this.token)),this.requestUrlRootObj="".concat(this.serverUrl,"/objects/").concat(this.streamId,"/").concat(this.objectId,"/single"),this.requestUrlChildren="".concat(this.serverUrl,"/api/getobjects/").concat(this.streamId),this.promises=[],this.intervals={},this.buffer=[],this.isLoading=!1,this.totalChildrenCount=0,this.traversedReferencesCount=0,this.options=c,this.options.numConnections=this.options.numConnections||4,this.cacheDB=null,this.lastAsyncPause=Date.now(),this.existingAsyncPause=null,this.preferredFetch=c.fetch,this.fetch=function(){const e=this.preferredFetch||fetch;if(!e)throw new o("Couldn't find fetch implementation! If running in a node environment, make sure you pass it in through the constructor!");return e(...arguments)}}static createFromObjects(e){const s=e[0];return new class extends i{constructor(){super({serverUrl:"dummy",streamId:"dummy",undefined:void 0,objectId:s.id}),this.objectId=s.id}async getRootObject(){return s}async getTotalObjectCount(){return Object.keys((null==s?void 0:s.__closure)||{}).length}async*getObjectIterator(){const s=Date.now();let t=0;for await(const{id:s,obj:r}of this.getRawObjectIterator(e))this.buffer[s]=r,t+=1,yield r;this.logger("Loaded ".concat(t," objects in: ").concat((Date.now()-s)/1e3))}async*getRawObjectIterator(e){yield{id:e[0].id,obj:e[0]};if(e[0].__closure)for(const s of e)yield{id:s.id,obj:s}}}}static createFromJSON(e){const s=performance.now(),t=JSON.parse(e);return console.warn("JSON Parse Time -> ",performance.now()-s),this.createFromObjects(t)}async asyncPause(){Date.now()-this.lastAsyncPause>=100&&(this.lastAsyncPause=Date.now(),this.existingAsyncPause=new Promise((e=>setTimeout(e,0))),await this.existingAsyncPause,this.existingAsyncPause=null,Date.now()-this.lastAsyncPause>500&&this.logger("Loader Event loop lag: ",Date.now()-this.lastAsyncPause))}dispose(){this.buffer=[],this.promises=[],Object.values(this.intervals).forEach((e=>clearInterval(e.interval)))}async getTotalObjectCount(){const e=await this.getRootObject();return Object.keys((null==e?void 0:e.__closure)||{}).length}async getRootObject(){const e=await this.getRawRootObject();let s;try{s=JSON.parse(e)}catch(s){throw new Error('Error parsing root object. "'.concat(s.message,"\". Root object: '").concat(e,"'"))}return s}async getAndConstructObject(e){await this.downloadObjectsInBuffer(e);const s=await this.getObject(this.objectId);return this.traverseAndConstruct(s,e)}async downloadObjectsInBuffer(e){let s=!0,t=0;for await(const r of this.getObjectIterator())s&&(this.totalChildrenCount=r.totalChildrenCount,s=!1,this.isLoading=!0),t++,e&&e({stage:"download",current:t,total:this.totalChildrenCount});this.isLoading=!1}async traverseAndConstruct(e,s){if(e){if("object"!=typeof e)return e;if(Array.isArray(e)&&0!==e.length){var t;const r=[];for(const t of e){if(!t)continue;if("object"!=typeof t&&!this.options.fullyTraverseArrays)return e;const o=t.referencedId?await this.getObject(t.referencedId):t;t.referencedId&&s&&s({stage:"construction",current:++this.traversedReferencesCount>this.totalChildrenCount?this.totalChildrenCount:this.traversedReferencesCount,total:this.totalChildrenCount}),r.push(await this.traverseAndConstruct(o,s))}return null!==(t=r[0])&&void 0!==t&&null!==(t=t.speckle_type)&&void 0!==t&&t.toLowerCase().includes("datachunk")?r.reduce(((e,s)=>e.concat(s.data)),[]):r}for(const s of this.options.excludeProps)delete e[s];for(const t in e)"object"==typeof e[t]&&null!==e[t]&&(e[t].referencedId&&(e[t]=await this.getObject(e[t].referencedId),s&&s({stage:"construction",current:++this.traversedReferencesCount>this.totalChildrenCount?this.totalChildrenCount:this.traversedReferencesCount,total:this.totalChildrenCount})),e[t]=await this.traverseAndConstruct(e[t],s));return e}}async getObject(e){if(this.buffer[e])return this.buffer[e];return new Promise(((s,t)=>{if(this.promises.push({id:e,resolve:s,reject:t}),this.intervals[e])this.intervals[e].elapsed=0;else{const s=setInterval(this.tryResolvePromise.bind(this),this.INTERVAL_MS,e);this.intervals[e]={interval:s,elapsed:0}}}))}tryResolvePromise(e){if(this.intervals[e].elapsed+=this.INTERVAL_MS,this.buffer[e]){for(const s of this.promises.filter((s=>s.id===e)))s.resolve(this.buffer[e]);return clearInterval(this.intervals[e].interval),void delete this.intervals[e]}this.intervals[e].elapsed>this.TIMEOUT_MS&&(this.warner("Timeout resolving ".concat(e,". HIC SVNT DRACONES.")),clearInterval(this.intervals[e].interval),this.promises.filter((s=>s.id===e)).forEach((e=>e.reject())),this.promises=this.promises.filter((e=>e.id!=e.id)))}async*getObjectIterator(){const e=Date.now();let s=0;for await(const e of this.getRawObjectIterator()){const{id:t,obj:r}=this.processLine(e);this.buffer[t]=r,s+=1,yield r}this.logger("Loaded ".concat(s," objects in: ").concat((Date.now()-e)/1e3))}processLine(e){const s=e.split("\t"),[t,r]=s;let o;try{o=JSON.parse(r)}catch(e){throw new Error("Error parsing object ".concat(t,": ").concat(e.message))}return{id:t,obj:o}}supportsCache(){return!(!this.options.enableCaching||!globalThis.indexedDB)}async setupCacheDb(){if(!this.supportsCache()||null!==this.cacheDB)return;await function(){if(navigator.userAgentData||!/Safari\//.test(navigator.userAgent)||/Chrom(e|ium)\//.test(navigator.userAgent)||!indexedDB.databases)return Promise.resolve();let e;return new Promise((s=>{const t=()=>indexedDB.databases().finally(s);e=setInterval(t,100),t()})).finally((()=>clearInterval(e)))}();const e=indexedDB.open("speckle-object-cache",1);e.onupgradeneeded=()=>e.result.createObjectStore("objects"),this.cacheDB=await this.promisifyIdbRequest(e)}async*getRawObjectIterator(){await this.setupCacheDb();const s=await this.getRawRootObject();yield"".concat(this.objectId,"\t").concat(s);const t=JSON.parse(s);if(!t.__closure)return;let r=Object.keys(t.__closure).filter((e=>!e.includes("blob"))).sort(((e,s)=>t.__closure[e]-t.__closure[s]));if(0===r.length)return;let o=[];if(r.length>50){const s=[[],[],[],[]];let i=0;for(;i<.05*r.length;i++)s[0].push(r[i]);for(;i<.2*r.length;i++)s[1].push(r[i]);for(;i<.6*r.length;i++)s[2].push(r[i]);for(;i<r.length;i++)s[3].push(r[i]);this.logger("Cache check for: ",s);const n=[];let a=this.cacheGetObjects(s[0]);for(let r=0;r<4;r++){const o=await a;r<3&&(a=this.cacheGetObjects(s[r+1]));const i=Object.keys(o).sort(((e,s)=>t.__closure[e]-t.__closure[s]));for(const e of i)yield"".concat(e,"\t").concat(o[e]);const c=s[r].filter((e=>!(e in o))),u=500,l=e.chunk(c,u);for(let e=0;e<l.length;e++)n.push.apply(n,l[e])}if(0===n.length)return;if(n.length<=50)o.push(n);else{for(o=[[],[],[],[]],i=0;i<.05*n.length;i++)o[0].push(n[i]);for(;i<.2*n.length;i++)o[1].push(n[i]);for(;i<.6*n.length;i++)o[2].push(n[i]);for(;i<n.length;i++)o[3].push(n[i])}}else{const e=await this.cacheGetObjects(r),s=Object.keys(e).sort(((e,s)=>t.__closure[e]-t.__closure[s]));for(const t of s)yield"".concat(t,"\t").concat(e[t]);if(r=r.filter((s=>!(s in e))),0===r.length)return;o.push(r)}const i=[],n=[],a=[],c=[],u=[];for(let e=0;e<o.length;e++)i.push(new TextDecoder),n.push(null),a.push(null),c.push(""),u.push(!1),this.fetch(this.requestUrlChildren,{method:"POST",headers:{...this.headers,"Content-Type":"application/json"},body:JSON.stringify({objects:JSON.stringify(o[e])})}).then((s=>{s.body.getReader&&(s.body.iterator=async function*(){const e=this.getReader();for(;;){const s=await e.read();if(s.done)return s.value;yield s.value}});const t=s.body.iterator();n[e]=t;const r=t.next().then((s=>(s.reqId=e,s)));a[e]=r}));for(;;){const e=a.filter((e=>!!e));if(0===e.length){if(u.every((e=>e)))break;await new Promise((e=>{setTimeout(e,10)}));continue}const s=await Promise.any(e);let{value:t,done:r,reqId:o}=s;if(u[o]=r,r)c[o].length>0&&(yield c[o],c[o]=""),a[o]=null;else{const e=n[o].next().then((e=>(e.reqId=o,e)));a[o]=e}if(!t)continue;t=i[o].decode(t);const l=(c[o]+t).split(/\r\n|\n|\r/),d=l.pop();c[o]=d;for(const e of l)yield e;this.cacheStoreObjects(l)}}async getRawRootObject(){const e=await this.cacheGetObjects([this.objectId]);if(e[this.objectId])return e[this.objectId];const s=await this.fetch(this.requestUrlRootObj,{headers:this.headers});if(!s.ok){if([401,403].includes(s.status))throw new o("You do not have access to the root object! Object URI: '".concat(this.requestUrlRootObj,"', Token ID: '").concat(this.token.substring(0,10),"'. Response: '").concat(s.status," ").concat(s.statusText,"'"));throw new o("Failed to fetch root object. Object URI: '".concat(this.requestUrlRootObj,"'. Response: '").concat(s.status," ").concat(s.statusText,"'"))}const t=await s.text();return this.cacheStoreObjects(["".concat(this.objectId,"\t").concat(t)]),t}promisifyIdbRequest(e){return new Promise(((s,t)=>{e.oncomplete=e.onsuccess=()=>s(e.result),e.onabort=e.onerror=()=>t(e.error)}))}async cacheGetObjects(s){if(!this.supportsCache())return{};null===this.cacheDB&&await this.setupCacheDb();const t={};for(let r=0;r<s.length;r+=500){const o=s.slice(r,r+500),i=this.cacheDB.transaction("objects","readonly").objectStore("objects"),n=o.map((e=>this.promisifyIdbRequest(i.get(e)).then((s=>({id:e,data:s}))))),a=await Promise.all(n);for(const s of a)!s.data||e.isString(s.data)&&s.data.startsWith("<html")||(t[s.id]=s.data)}return t}async cacheStoreObjects(s){if(!this.supportsCache())return{};null===this.cacheDB&&await this.setupCacheDb();try{const t=this.cacheDB.transaction("objects","readwrite").objectStore("objects");for(const r of s){const[s,o]=r.split("\t");o&&e.isString(o)&&!o.startsWith("<html")&&t.put(o,s)}return this.promisifyIdbRequest(t.transaction)}catch(e){this.logger.error(e)}return Promise.resolve()}}module.exports=i;