diff --git a/com/isoterminal.js b/com/isoterminal.js index b10bd12..ddceaf7 100644 --- a/com/isoterminal.js +++ b/com/isoterminal.js @@ -304,15 +304,17 @@ if( typeof AFRAME != 'undefined '){ remotekeyboard: "com/isoterminal/feat/remotekeyboard.js", indexhtml: "com/isoterminal/feat/index.html.js", indexjs: "com/isoterminal/feat/index.js.js", - autorestore: "com/isoterminal/feat/autorestore.js", pastedropFeat: "com/isoterminal/feat/pastedrop.js", httpfs: "com/isoterminal/feat/httpfs.js", + autorestore: "com/isoterminal/feat/autorestore.js", } if( this.data.emulator == 'fbterm' ){ features['fbtermjs'] = "com/isoterminal/term.js" features['fbterm'] = "com/isoterminal/feat/term.js" } await AFRAME.utils.require(features) + // this one extends autorestore.js + await AFRAME.utils.require({remotestorage: "com/isoterminal/feat/remotestorage.js"}) this.el.setAttribute("selfcontainer","") diff --git a/com/isoterminal/ISOTerminal.js b/com/isoterminal/ISOTerminal.js index 6b20e66..d7af75f 100644 --- a/com/isoterminal/ISOTerminal.js +++ b/com/isoterminal/ISOTerminal.js @@ -256,6 +256,7 @@ ISOTerminal.prototype.startVM = function(opts){ this.v86opts = opts this.addEventListener('emulator-started', async (e) => { + if( this.boot.fromImage ) return this.emit('serial-output-string', "\r[!] downloading session...please wait\n\r[!] this could take a while depending on your connection..\n\r") let line = '' this.ready = false @@ -278,12 +279,17 @@ ISOTerminal.prototype.startVM = function(opts){ } ISOTerminal.prototype.bootISO = function(){ + const getImage = (str) => decodeURIComponent( + str.match(/\&img=/) ? str.replace(/.*img=/,'').replace(/\&.*/,'') : '' + ) let msglib = this.getLoaderMsg() this.emit('status',msglib.loadmsg) let msg = "\n\r" + msglib.empowermsg + msglib.text_color + msglib.loadmsg + msglib.text_reset this.emit('serial-output-string', msg) - this.emit('runISO',{...this.v86opts, bufferLatency: this.opts.bufferLatency }) - + if( getImage(this.boot.hash) ){ + this.boot.fromImage = true + } + this.emit('runISO',{...this.v86opts, bufferLatency: this.opts.bufferLatency, img: getImage(this.boot.hash) }) } diff --git a/com/isoterminal/feat/autorestore.js b/com/isoterminal/feat/autorestore.js index aa9341c..8e58c3c 100644 --- a/com/isoterminal/feat/autorestore.js +++ b/com/isoterminal/feat/autorestore.js @@ -1,8 +1,16 @@ +// this is restoring state to/from the v86 emulator +// however instead of passing the huge blob between webworker/browser +// we transfer it via localforage as a base64 string + if( typeof emulator != 'undefined' ){ // inside worker-thread importScripts("localforage.js") // we don't instance it again here (just use its functions) this.restore_state = async function(data){ + // fastforward instance state + this.opts.muteUntilPrompt = false + this.ready = true + return new Promise( (resolve,reject) => { localforage.getItem("state", async (err,stateBase64) => { if( stateBase64 && !err ){ @@ -15,15 +23,20 @@ if( typeof emulator != 'undefined' ){ }) } this.save_state = async function(){ - console.log("saving session") - let state = await emulator.save_state() - localforage.setDriver([ - localforage.INDEXEDDB, - localforage.WEBSQL, - localforage.LOCALSTORAGE - ]).then( () => { - localforage.setItem("state", ISOTerminal.prototype.convert.arrayBufferToBase64(state) ) - console.log("state saved") + return new Promise( async (resolve,reject ) => { + console.log("saving session") + let state = await emulator.save_state() + localforage.setDriver([ + localforage.INDEXEDDB, + localforage.WEBSQL, + localforage.LOCALSTORAGE + ]) + .then( () => { + localforage.setItem("state", ISOTerminal.prototype.convert.arrayBufferToBase64(state) ) + console.log("state saved") + resolve() + }) + .catch( reject ) }) } @@ -32,42 +45,44 @@ if( typeof emulator != 'undefined' ){ // inside browser-thread ISOTerminal.addEventListener('emulator-started', function(e){ this.autorestore(e) + this.emit("autorestore-installed") }) - ISOTerminal.prototype.autorestore = async function(e){ + ISOTerminal.prototype.restore = async function(e){ - localforage.setDriver([ - localforage.INDEXEDDB, - localforage.WEBSQL, - localforage.LOCALSTORAGE - ]).then( () => { + const onGetItem = (err,stateBase64) => { + const askConfirm = () => { + if( window.localStorage.getItem("restorestate") == "true" ) return true + try{ + const scene = document.querySelector('a-scene'); + if( scene.is('ar-mode') ) scene.exitAR() + if( scene.is('vr-mode') ) scene.exitVR() + }catch(e){} + return confirm( "Continue old session?" ) + } - localforage.getItem("state", async (err,stateBase64) => { - const askConfirm = () => { - if( window.localStorage.getItem("restorestate") == "true" ) return true - try{ - const scene = document.querySelector('a-scene'); - if( scene.is('ar-mode') ) scene.exitAR() - if( scene.is('vr-mode') ) scene.exitVR() - }catch(e){} - return confirm('continue last session?') - } - if( stateBase64 && !err && document.location.hash.length < 2 && askConfirm() ){ - this.noboot = true // see feat/boot.js - try{ - await this.worker.restore_state() + if( stateBase64 && !err && document.location.hash.length < 2 && askConfirm() ){ + this.noboot = true // see feat/boot.js + try{ + this.worker.restore_state() + .then( () => { // simulate / fastforward boot events this.postBoot( () => { - this.send("l\n") - this.send("hook wakeup\n") + // force redraw terminal issue + this.send("l") + setTimeout( () => this.send("l"), 200 ) + //this.send("12") + this.emit("exec",["source /etc/profile.sh; hook wakeup\n"]) + this.emit("restored") }) - }catch(e){ console.error(e) } - } - }) - - this.save = async () => { - await this.worker.save_state() + }) + }catch(e){ console.error(e) } } + } + + const doRestore = () => { + + localforage.getItem("state", (err,stateBase64) => onGetItem(err,stateBase64) ) window.addEventListener("beforeunload", function (e) { var confirmationMessage = "Sure you want to leave?\nTIP: enter 'save' to continue this session later"; @@ -75,7 +90,17 @@ if( typeof emulator != 'undefined' ){ return confirmationMessage; //Webkit, Safari, Chrome }); - }) + } + + localforage.setDriver([ + localforage.INDEXEDDB, + localforage.WEBSQL, + localforage.LOCALSTORAGE + ]) + .then( () => doRestore() ) + } + ISOTerminal.prototype.autorestore = ISOTerminal.prototype.restore // alias to launch during boot + } diff --git a/com/isoterminal/feat/boot.js b/com/isoterminal/feat/boot.js index 0f0db3e..6674117 100644 --- a/com/isoterminal/feat/boot.js +++ b/com/isoterminal/feat/boot.js @@ -1,5 +1,5 @@ ISOTerminal.addEventListener('ready', function(e){ - setTimeout( () => this.boot(), 50 ) // because of autorestore.js + setTimeout( () => this.boot(), 50 ) // allow other features/plugins to settle first (autorestore.js e.g.) }) ISOTerminal.prototype.bootMenu = function(e){ @@ -17,7 +17,7 @@ ISOTerminal.prototype.bootMenu = function(e){ }else{ // autoboot if( this.term ){ - this.term.handler( e.detail.bootMenu || e.detail.bootMenuURL ) + this.term.handler( String(e.detail.bootMenu || e.detail.bootMenuURL).charAt(0) ) this.term.handler("\n") } } @@ -33,10 +33,17 @@ ISOTerminal.prototype.boot = async function(e){ 'export BROWSER=1', ] for ( let i in document.location ){ - if( typeof document.location[i] == 'string' ){ + if( typeof document.location[i] == 'string' && !String(i).match(/(hash|search)/) ){ env.push( 'export '+String(i).toUpperCase()+'="'+decodeURIComponent( document.location[i]+'"') ) } } + + // we export the cached hash/query (because they might be gone due to remotestorage plugin) + if( this.boot.hash.charAt(2) == '&' ){ // strip bootoption + this.boot.hashExBoot = `#` + this.boot.hash.substr(3) + } + env.push( 'export HASH="'+decodeURIComponent( this.boot.hashExBoot || this.boot.hash ) +'"' ) + env.push( 'export QUERY="'+decodeURIComponent( this.boot.query ) +'"' ) await this.worker.create_file("profile.browser", this.convert.toUint8Array( env.join('\n') ) ) if( this.serial_input == 0 ){ @@ -47,8 +54,12 @@ ISOTerminal.prototype.boot = async function(e){ } -// here REPL's can be defined +ISOTerminal.prototype.boot.fromImage = false ISOTerminal.prototype.boot.menu = [] +ISOTerminal.prototype.boot.hash = document.location.hash +ISOTerminal.prototype.boot.query = document.location.search + +// here REPL's can be defined // REPL: iso if( typeof window.PromiseWorker != 'undefined' ){ // if xrsh v86 is able to run in in worker diff --git a/com/isoterminal/feat/remotestorage.js b/com/isoterminal/feat/remotestorage.js new file mode 100644 index 0000000..3d654e2 --- /dev/null +++ b/com/isoterminal/feat/remotestorage.js @@ -0,0 +1,365 @@ +/* Remote storage feature + * + * NOTE: this feature extends the localstorage functions. + * this file can be excluded without crippling the + * core localstorage mechanism. + */ + +// see https://remotestorage.io/rs.js/docs/data-modules/ +ISOTerminal.prototype.remoteStorageModule = { + name: 'xrsh', + builder: function(privateClient, publicClient) { + return { + exports: { + add: function(data,opts) { + if( !data || !opts.filename || !opts.mimetype) throw 'webxr.add() needs filedata + filename + mimetype' + const client = opts.client = opts.public ? publicClient : privateClient; + return client.storeFile(opts.mimetype, opts.filename,data) + }, + + getListing: function(a,opts){ + opts = opts || {} + const client = opts.public ? publicClient : privateClient; + return client.getListing(a) + }, + + getFile: function(file,opts){ + opts = opts || {} + const client = opts.public ? publicClient : privateClient; + return client.getFile(file) + }, + + remove: function(file,opts){ + opts = opts || {} + const client = opts.public ? publicClient : privateClient; + return client.remove(file) + } + + } + } + } +}; + +// this is the HTML which we append to the remotestorage widget: +// https://remotestorage.io/rs.js/docs/getting-started/connect-widget.html +const _rs_widget_html = ` +