/* provides a web-component to execute a PatchBoot app. It is passed the app msg rather than the blob to support
* different app formats in future.
*/
import { default as pull, paraMap, collect } from 'pull-stream'
import MRPC from 'muxrpc'
import Util from '../Util.js';
class AppRunner extends HTMLElement {
constructor() {
super();
}
connectedCallback() {
const runnerArea = this.attachShadow({ mode: 'open' })
const util = new Util(this.sbot)
const getClassicAppFrameContent = () => {
const blobId = this.app.link
return util.dereferenceUriOrSigil(blobId).then(code => {
function utf8_to_b64(str) {
return btoa(unescape(encodeURIComponent(str)));
}
return `
Patchboot app
`
})
}
const addBaseUrl = (htmlString) => htmlString.replace('',``)
const getWebappContent = () => {
const link = this.app.link
// because of same originy policy we cab't just use original link
return util.dereferenceUriOrSigil(link).then(content => {
content = (link.startsWith('&') || link.startsWith('ssb')) ? content : addBaseUrl(content)
return content
})
}
const getAppFrameContent = () => {
if (this.app.type === 'patchboot-app') {
return getClassicAppFrameContent()
} else if (this.app.type === 'patchboot-webapp') {
return getWebappContent()
} else {
throw new Error('unsupported: ' + this.app.type)
}
}
const createIFrame = () => {
const iFrame = document.createElement('iframe')
runnerArea.appendChild(iFrame) // has to appended before contentWindow is accessed
iFrame.style = "width: 100%; height: 100%; border: none;"
return getAppFrameContent().then(iFrameContent => {
iFrame.contentWindow.document.open()
iFrame.contentWindow.document.write(iFrameContent)
iFrame.contentWindow.document.close()
return iFrame
})
}
createIFrame().then(iFrame => {
console.log(iFrame)
this.dispatchEvent(new Event('loaded'))
let messageDataCallback = null
let messageDataBuffer = []
const fromPage = function read(abort, cb) {
if (messageDataBuffer.length > 0) {
const data = messageDataBuffer[0]
messageDataBuffer = messageDataBuffer.splice(1)
cb(null, data)
} else {
messageDataCallback = cb
}
}
function ping() {
iFrame.contentWindow.postMessage({
direction: "from-content-script",
action: 'ping'
}, '*');
}
iFrame.contentWindow.addEventListener("message", (event) => {
if (event.data && event.data.direction === "from-page-script") {
if (event.data.action === "ping") {
ping()
} else {
//new Uint8Array(event.data.message) is not accepted by muxrpc
const asBuffer = Buffer.from(event.data.message)
if (messageDataCallback) {
const _messageDataCallback = messageDataCallback
messageDataCallback = null
_messageDataCallback(null, asBuffer)
} else {
console.log('buffering....')
messageDataBuffer.push(asBuffer)
}
}
}
})
const toPage = function (source) {
source(null, function more(end, data) {
iFrame.contentWindow.postMessage({
direction: "from-content-script",
message: data
}, '*');
source(null, more)
})
}
iFrame.contentWindow.addEventListener('load', () => this.dispatchEvent(new CustomEvent('ready')))
/*function logger(text) {
return pull.map((v) => {
console.log(text,v)
console.log(new TextDecoder("utf-8").decode(v))
return v
})
}*/
this.sbot.manifest().then(manifest => {
//console.log('manifest', JSON.stringify(manifest))
const asyncManifest = asyncifyManifest(manifest)
const server = MRPC(null, asyncManifest)(this.sbot)
const serverStream = server.createStream(() => { console.log('closed') })
pull(fromPage, serverStream, toPage)
})
})
}
}
function asyncifyManifest(manifest) {
if (typeof manifest !== 'object') return manifest
let asyncified = {}
for (let k in manifest) {
var value = manifest[k]
// Rewrite re-exported sync methods as async,
if (value === 'sync') {
value = 'async'
}
asyncified[k] = value
}
return asyncified
}
customElements.define("app-runner", AppRunner)